Разбираемся с 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




    4.0.0

    
        ru.sportmaster
        vespa
        1.0-SNAPSHOT
    

    vespa-config
    
    container-plugin

    
        
        
            com.yahoo.vespa
            container
            ${vespa.version}
            provided
        
    

    
        
            
            
                com.yahoo.vespa
                bundle-plugin
                ${vespa.version}
                true
                
                    
                    true
                
            
            
            
                com.yahoo.vespa
                vespa-application-maven-plugin
                ${vespa.version}
                
                    
                        
                            packageApplication
                        
                    
                
            
        
    

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

src/main/application/services.xml




    
        
        
        
        
    

    
    
        
        
            
            
        
        
        2
        
        
            
            
            
            
        
    
  

Данная конфигурация создаст 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 {
            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 — массив простых типов или структура данных. Каждый элемент этого типа индексируется отдельно.

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

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

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

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

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

  • annotationreference — поле с ссылкой на аннотацию.

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

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

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

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

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

Отдельно стоит обсудить свойство 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


    
    indexing-change
    
    indexing-mode-change
    
    field-type-change
    
    tensor-type-change
    
    resources-reduction
    
    content-cluster-removal
    
    global-document-change
    
    global-endpoint-change
    
    redundancy-increase
    
    redundancy-one
    
    certificate-removal

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

Заключение

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

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

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

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

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

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

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

© Habrahabr.ru