Настройка приближенного поиска kNN
Elasticsearch поддерживает приближенный поиск k ближайших соседей для эффективного поиска k ближайших векторов к запросу. Поскольку приближенный поиск k ближайших соседей работает по-другому, чем другие запросы, существуют особые соображения относительно его производительности.
Многие из этих рекомендаций помогают улучшить скорость поиска. При приближенном поиске k ближайших соседей алгоритм индексирования выполняет поиск в фоновом режиме, чтобы создать структуры индекса векторов. Таким образом, эти же рекомендации также помогают с производительностью индексирования.
Уменьшить размер векторов в памяти
По умолчанию element_type равен float. Однако это можно автоматически квантовать во время индексирования с помощью quantization. Квантование уменьшит требуемое количество памяти в 4, 8 или до 32 раз, но также уменьшит точность векторов и увеличит использование дискового пространства для поля (соответственно на 25%, 12,5% или 3,125%). Увеличение использования дискового пространства является следствием того, что Elasticsearch хранит как квантованные, так и неквантованные векторы. Например, при квантовании с типом int8 40 ГБ векторов с плавающей запятой будет храниться дополнительных 10 ГБ данных для квантованных векторов. Общее использование дискового пространства составит 50 ГБ, но использование памяти для быстрого поиска уменьшится до 10 ГБ.
Для float векторов с dim, большим или равным 384, рекомендуется использовать quantized индекс.
Уменьшить размерность векторов
Скорость поиска k ближайших соседей линейно зависит от количества размерностей векторов, потому что каждый вычисление сходства учитывает каждый элемент в двух векторах. Всякий раз, когда это возможно, лучше использовать векторы с меньшей размерностью. Некоторые модели встраивания доступны в разных "размерах", с вариантами как меньшей, так и большей размерности. Вы также можете экспериментировать с методами уменьшения размерности, такими как PCA. При экспериментировании с различными подходами важно измерять влияние на релевантность, чтобы убедиться, что качество поиска по-прежнему приемлемо.
Исключить поля векторов из _source
Elasticsearch сохраняет исходный JSON-документ, переданный во время индексирования, в _source поле. По умолчанию каждый результат поиска содержит весь документ _source. Когда документы содержат поля dense_vector высокой размерности, то _source может быть достаточно большим и дорогим в загрузке. Это может значительно замедлить скорость поиска k ближайших соседей.
Операции переиндексации, обновления и обновления по запросу обычно требуют поля _source. Отключение поля _source может привести к непредсказуемому поведению для этих операций. Например, при переиндексации новый индекс может не содержать поле dense_vector.
Вы можете отключить хранение dense_vector полей в _source через параметр отображения excludes. Это предотвращает загрузку и возврат больших векторов во время поиска, а также уменьшает размер индекса. Векторы, исключенные из _source, все еще могут быть использованы в поиске k ближайших соседей, поскольку он использует отдельные структуры данных для выполнения поиска. Перед использованием параметра excludes, обязательно ознакомьтесь с недостатками исключения полей из _source.
Другой вариант — использовать синтетический _source.
Обеспечить достаточную память узлов данных
Elasticsearch использует алгоритм HNSW для приближенного поиска k ближайших соседей. HNSW — алгоритм на основе графов, который эффективно работает только тогда, когда большинство данных векторов хранится в памяти. Необходимо убедиться, что узлы данных имеют достаточный объем оперативной памяти для хранения данных векторов и структур индекса. Чтобы проверить размер данных векторов, можно использовать API Анализ использования дискового пространства индекса.
Ниже приведены оценки для различных типов элементов и уровней квантования:
-
element_type: float:num_vectors * num_dimensions * 4 -
element_type: floatсquantization: int8:num_vectors * (num_dimensions + 4) -
element_type: floatсquantization: int4:num_vectors * (num_dimensions/2 + 4) -
element_type: floatсquantization: bbq:num_vectors * (num_dimensions/8 + 12) -
element_type: byte:num_vectors * num_dimensions -
element_type: bit:num_vectors * (num_dimensions/8)
Если используется HNSW, график также должен находиться в памяти, чтобы оценить необходимые байты, используйте num_vectors * 4 * HNSW.m. Значение по умолчанию для HNSW.m равно 16, поэтому по умолчанию num_vectors * 4 * 16.
Обратите внимание, что требуемая оперативная память предназначена для кэша файловой системы, который отделен от кучи Java.
Узлы данных также должны оставлять буфер для других способов потребления памяти. Например, ваш индекс может также включать текстовые поля и числовые значения, которые также могут использовать кэш файловой системы. Рекомендуется проводить бенчмаркинг со своим набором данных, чтобы убедиться, что есть достаточное количество памяти для хорошей производительности поиска. Вы можете найти примеры наборов данных и конфигураций, используемых в наших ежедневных тестах, здесь и здесь.
Разогрев кэша файловой системы
Если машина, на которой работает Elasticsearch, перезапускается, кэш файловой системы будет пустым, поэтому потребуется некоторое время, прежде чем операционная система загрузит горячие области индекса в память, чтобы операции поиска были быстрыми. Вы можете явно указать операционной системе, какие файлы должны быть загружены в память с самого начала, в зависимости от расширения файла, используя настройку index.store.preload.
Загрузка данных в кэш файловой системы с самого начала для слишком большого количества индексов или файлов замедлит поиск, если кэш файловой системы недостаточно велик, чтобы вместить все данные. Используйте с осторожностью.
Следующие расширения файлов используются для приближенного поиска k ближайших соседей: каждое расширение разбито по типам квантования.
-
vexдля графика HNSW -
vecдля всех значений векторов без квантования. Это включает все типы элементов:float,byteиbit. -
veqдля квантованных векторов, индексированных с помощьюquantization:int4илиint8 -
vebдля бинарных векторов, индексированных с помощьюquantization:bbq -
vem,vemf,vemqиvembдля метаданных, обычно малых и не вызывающих озабоченностей по поводу предварительной загрузки
В целом, если вы используете квантованный индекс, вы должны предварительно загрузить только соответствующие квантованные значения и график HNSW. Предварительная загрузка исходных векторов не требуется и может быть неэффективна.
Уменьшить количество сегментов индекса
Фрагменты Elasticsearch состоят из сегментов, которые являются внутренними элементами хранения в индексе. Для приближенного поиска k ближайших соседей Elasticsearch хранит значения векторов каждого сегмента как отдельный график HNSW, поэтому поиск k ближайших соседей должен проверять каждый сегмент. Недавняя параллелизация поиска k ближайших соседей сделала его намного быстрее при поиске по нескольким сегментам, но поиск k ближайших соседей все еще может быть в несколько раз быстрее, если сегментов меньше. По умолчанию Elasticsearch периодически объединяет меньшие сегменты в большие через фоновый процесс слияния. Если этого недостаточно, можно принять явные меры для уменьшения количества сегментов индекса.
Увеличить максимальный размер сегмента
Elasticsearch предоставляет множество настраиваемых параметров для управления процессом слияния. Важным параметром является index.merge.policy.max_merged_segment. Он контролирует максимальный размер сегментов, создаваемых во время процесса слияния. Увеличение значения позволяет уменьшить количество сегментов в индексе. Значение по умолчанию равно 5GB, но это может быть слишком мало для векторов большей размерности. Рассмотрите возможность увеличения этого значения до 10GB или 20GB может помочь уменьшить количество сегментов.
Создание больших сегментов во время массовой индексации
Обычный метод — сначала выполнить начальную массовую загрузку, а затем сделать индекс доступным для поиска. Вместо принудительного слияния вы можете изменить настройки индекса, чтобы стимулировать Elasticsearch создавать более крупные начальные сегменты:
- Убедитесь, что во время массовой загрузки нет поисков, и отключите
index.refresh_interval, установив его значение в-1. Это предотвращает операции обновления и позволяет избежать создания дополнительных сегментов. - Дайте Elasticsearch большой буфер для индексирования, чтобы он мог принимать больше документов перед сбросом. По умолчанию
indices.memory.index_buffer_sizeустановлен на 10% от размера кучи. При значительном размере кучи, например 32 ГБ, этого часто достаточно. Для использования всего буфера индексирования также необходимо увеличить ограничениеindex.translog.flush_threshold_size.
Избегайте интенсивного индексирования во время поиска
Активное индексирование документов может негативно сказаться на производительности приближенного поиска kNN, так как индексирующие потоки отнимают вычислительные ресурсы у поиска. При одновременном индексировании и поиске Elasticsearch также часто обновляет фрагменты, что создает несколько маленьких сегментов. Это также ухудшает производительность поиска, так как приближенный поиск kNN медленнее при большем количестве сегментов.
В случае возможности следует избегать интенсивного индексирования во время приближенного поиска kNN. Если вам необходимо переиндексировать все данные, возможно, потому что изменилась модель встраивания векторов, то лучше переиндексировать новые документы в отдельный индекс, а не обновлять их на месте. Это помогает избежать замедления, упомянутого выше, и предотвращает дорогостоящие операции слияния из-за частых обновлений документов.
Избегайте перегрузки кэша страниц, используя умеренные значения readahead в Linux
Поиск может вызывать много случайного ввода-вывода. Когда у базового устройства блока большое значение readahead, может быть выполнено много ненужного ввода-вывода, особенно когда файлы доступны с помощью сопоставления памяти (см. типы хранилища).
Большинство дистрибутивов Linux используют разумное значение readahead в 128KiB для одного простого устройства, однако при использовании программного RAID, LVM или dm-crypt результирующее устройство блока (поддерживающее путь Elasticsearch path.data) может иметь очень большое значение readahead (в диапазоне нескольких МБ). Это обычно приводит к серьезной перегрузке кэша страниц (файловой системы), негативно влияя на производительность поиска (или обновления).
Вы можете проверить текущее значение в KiB, используя lsblk -o NAME,RA,MOUNTPOINT,TYPE,SIZE. Обратитесь к документации вашего дистрибутива, чтобы узнать, как изменить это значение (например, с помощью правила udev для сохранения после перезагрузки или через blockdev --setra как временной настройки). Мы рекомендуем значение 128KiB для readahead.
blockdev ожидает значения в секторах по 512 байт, в то время как lsblk сообщает значения в KiB. В качестве примера, чтобы временно установить readahead на 128KiB для /dev/nvme0n1, укажите blockdev --setra 256 /dev/nvme0n1.
© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/tune-knn-search.html