В первой части этой статьи мы подробно рассмотрели, какие индексы в настоящее время доступны в ArangoDB (3.2 и 3.3), а также кратко рассмотрели, какие улучшения появятся в ArangoDB 3.4. "Читай полную статью здесь".
В этой части II мы сосредоточимся на том, как на самом деле добавлять индексы в модель данных и ускорять определенные запросы.
Добавление индексов в модель данных
Целью добавления дополнительного индекса в модель данных является ускорение определенного запроса или даже нескольких запросов.
Одной из первых вещей, которые следует сделать при разработке запросов AQL, является проверка вывода команды explain
. Запрос можно объяснить с помощью веб-интерфейса ArangoDB или из ArangoShell. В ArangoShell это так же просто, как db._explain(query)
, где query
— это строка запроса AQL. Чтобы объяснить запрос, который также имеет параметры связывания, их необходимо передать в команду отдельно, например. db._explain(query, bindParameters)
.
Вариант использования: добавление хеш-индекса, чтобы избежать полного сканирования коллекции
Когда нет индексов и запрос обращается к коллекции с большим количеством документов, он, скорее всего, в конечном итоге выполнит полное сканирование коллекции, а это означает, что проверяется каждый документ в коллекции (потенциально даже читается с диска). Это займет время, пропорциональное количеству документов в коллекции.
Рассмотрим следующий запрос к только что созданной коллекции test
(т. е. без индексов, кроме первичного индекса):
Это показывает, что при выполнении этого запроса не будут использоваться никакие индексы и что оптимизатор выбрал полное сканирование коллекции для просмотра всех документов в коллекции (примечание: это обозначено EnumerateCollectionNode
в приведенном выше плане выполнения. EnumerateCollectionNode
— это имя какого-либо компонента, сканирующего все документы в коллекции). Поскольку коллекция содержит 1 000 000 документов, этот запрос почти гарантированно будет медленным без надлежащего индекса.
Чтобы этот запрос работал лучше, явно необходим индекс для value
. Возможные типы индексов для нового индекса: hash
или skiplist
.
- Для MMFiles разница между этими двумя типами огромна, поскольку хеш-индекс будет иметь амортизированную сложность O (1) для всех операций, тогда как индекс списка пропуска имеет амортизированную сложность O (log n). Это означает, что индекс списка пропуска будет намного дороже, чем хэш-индекс в MMFiles.
- Для движка RocksDB оба типа индексов могут использоваться взаимозаменяемо.
Как правило, мы должны использовать хэш-индекс, если мы уверены, что все запросы будут искать value
по равенству (как указано выше). Если это неизвестно заранее, или если будут запросы, которые ищут value
с использованием поиска по диапазону, или сортируют по value
, нам может быть лучше использовать индекс списка пропуска, по крайней мере, для MMFiles.
Следующее решение для нового индекса заключается в том, можно ли сделать его уникальным (что может немного повысить производительность поиска) или нет. В этом случае мы знаем, что value
не будет уникальным в этой коллекции, поэтому нам нужно выбрать неуникальный индекс. Мы также знаем, что все документы в этой коллекции содержат атрибуты, отличные от null
value
, поэтому здесь мы вообще не выиграем от разреженного индекса.
Создав неуникальный, неразреженный хеш-индекс для value
, план выполнения того же запроса значительно улучшится до просто:
с примерно 200 документами для просмотра вместо 1 000 000. Это улучшение на несколько порядков.
Вариант использования: добавление комбинированного индекса
Предположим, у нас есть запрос, подобный следующему, который попытается найти документы для данной категории и с отметками времени, которые относятся к определенному периоду времени:
Теперь можно создать хеш-индекс для категории attribute
и индекс списка пропуска для timestamp
.
Это вызовет план выполнения, который использует только один из только что созданных индексов:
Почему запрос не использует оба индекса?
Как правило, оптимизатор запросов выбирает один индекс для цикла FOR в запросе AQL и использует его. Несмотря на то, что доступны два индекса, не гарантируется, что использование двух индексов даст лучший результат с точки зрения скорости выполнения. Таким образом, оптимизатор выберет тот индекс, для которого, по его мнению, потребуется проверить и вернуть меньшее количество документов.
Тем не менее это можно улучшить. Вместо того, чтобы создавать отдельные индексы для category
и timestamp
, здесь было бы намного лучше создать комбинированный индекс списка пропусков для category
и timestamp
(именно в таком порядке).
С этим индексом план выполнения упрощается до
что, скорее всего, будет соответствовать гораздо меньшему количеству документов, которые необходимо проверить, и, следовательно, значительно повысит производительность выполнения запросов. В качестве бонуса этот комбинированный индекс можно также использовать для фильтрации только по category
, для сортировки по category
или для сортировки по category
и timestamp
.
Например, если наш запрос
план его выполнения не будет более сложным благодаря комбинированному индексу, который может обрабатывать сортировку:
Примечание: сортировка по timestamp
здесь возможна только потому, что мы фильтруем по константе category
таким образом, что мы увидим только одно значение category
, возвращаемое этим запросом. Оптимизатор запросов достаточно умен, чтобы обнаружить это, и будет использовать индекс для сортировки, даже если category
не был указан в предложении SORT.
Один или несколько индексов
Запрос может использовать более одного индекса для коллекции, но для каждого цикла FOR в запросе AQL оптимизатор будет использовать не более одного индекса, если условия фильтрации сочетаются с логическим И. >. Оптимизатор попытается выбрать индекс, который обещает сократить работу (фильтрацию, сортировку) на наибольшую величину. Если имеется несколько индексов-кандидатов, будут рассмотрены все они, но будет выбран тот, который имеет наименьшую ожидаемую стоимость. Поэтому часто имеет смысл использовать комбинированные индексы.
Если есть условия фильтрации по разным атрибутам в сочетании с логическим ИЛИ, оптимизатор может выбрать более одного индекса даже для одного и того же цикла FOR.
Чего следует избегать
Создание слишком большого количества индексов
Создание дополнительных индексов сопряжено с затратами, состоящими из затрат на хранение и накладных расходов времени выполнения.
Движок MMFiles имеет индексы в памяти, поэтому каждый созданный индекс будет занимать часть дефицитной оперативной памяти. Для движка RocksDB индексы будут храниться на диске, поэтому они будут занимать место на диске. Кроме того, даже несмотря на то, что индексы движка RocksDB не будут создаваться в ОЗУ, они все равно могут использовать некоторое количество ОЗУ, поскольку значения индексов также могут попасть в некоторые кеши в памяти.
Что касается накладных расходов во время выполнения, вот список ситуаций, когда индексы влияют на производительность:
- Сначала создается индекс, и в него вставляются все документы из коллекций. Это займет время, пропорциональное количеству документов в коллекции, а время выполнения также будет зависеть от алгоритмической сложности внутренних компонентов индекса.
- При каждой операции вставки, обновления, замены или удаления документа также необходимо изменить индекс. Это увеличит время, необходимое для всех операций с документами, влияющих на индекс.
- Коллекция снова загружается после перезапуска сервера, а индекс в памяти строится из данных документа на диске (примечание: это относится только к движку MMFiles, этого не произойдет с движком RocksDB, поскольку в нем есть все свои индексы на диске и не создает их в памяти при загрузке коллекции).
Поэтому нужно создавать только те индексы, которые действительно необходимы. Нет смысла индексировать каждый существующий атрибут, потому что это гарантированно замедляет запись, но в то же время оказывает лишь сомнительное влияние на производительность поисковых запросов.
Часто имеет смысл индексировать атрибуты, которые используются в большинстве запросов, чтобы также улучшить большинство запросов. Еще полезнее находить комбинации атрибутов, которые используются вместе в нескольких запросах, а затем создавать по ним комбинированные индексы.
В случае сомнений следует измерить влияние дополнительных индексов на запросы и операции модификации данных, используя explain
и отслеживая время выполнения запросов в клиентских приложениях.
Однако не использовать индексы там, где они могли бы значительно улучшить ситуацию, также следует избегать по очевидным причинам.
Использование вызовов функций для атрибутов индекса
В отличие от некоторых других баз данных, ArangoDB не предоставляет никаких индексов, в которых могут храниться результаты функций или другие оцененные значения выражений. Использование функции для индексированного атрибута документа в условии FILTER, скорее всего, сделает индекс бесполезным для этого запроса.
Например, предположим, что для атрибута value
в коллекции test
имеется индекс. Следующий запрос попытается сравнить все значения из индекса в их варианте в нижнем регистре с постоянной строкой поиска. Однако он не будет использовать индекс, поскольку индекс не может предоставить результат функции LOWER
AQL. Поэтому вместо этого запрос выполнит полное сканирование коллекции:
Чтобы заставить этот запрос использовать индекс атрибута value
, есть два варианта, каждый из которых требует небольших изменений в модели данных.
- если возможно, сохраняйте значения
value
только в нижнем регистре. Затем требуется только преобразовать все значения поиска в нижний регистр. - добавьте в коллекцию дополнительный атрибут
lowercasedValue
для хранения версииvalue
в нижнем регистре исключительно с целью использования для него индекса. Затем необходимо создать индекс наlowercasedValue
, а не наvalue
.
Пример для последнего следующий:
Создание разреженных индексов
В некоторых случаях объявление разреженного индекса не позволит оптимизатору запросов использовать его.
Например, это объединение двух коллекций превратится в кошмар с разреженными индексами.
Причина неиспользования здесь индексов заключается в том, что в каждой коллекции могут быть документы, не имеющие атрибута value
, или содержащие атрибут value
со значением null
. Если бы оптимизатор использовал любой из разреженных индексов на value
, он не нашел бы эти документы (поскольку они не были бы проиндексированы). Таким образом, без использования индексов запрос будет давать больше результатов, чем с использованием индексов. Это ситуация, которую оптимизатор должен избегать, поэтому он отбросит разреженные индексы и продолжит работу без них. Обратите внимание, что здесь по-прежнему будут использоваться неразреженные индексы, если они есть, но смысл здесь в том, чтобы показать, что объявление разреженного индекса может иметь нежелательные последствия для некоторых запросов.
Таким образом, индекс должен быть объявлен разреженным только тогда, когда все запросы, использующие этот атрибут, были проверены, и было обнаружено, что индекс все еще может использоваться для них, даже если он разреженный.
Что дальше
В настоящее время мы работаем над ArangoSearch (входит в состав ArangoDB 3.4), который станет превосходной заменой существующему полнотекстовому индексу. Он предлагает рейтинг релевантности, позволяет индексировать несколько атрибутов и поддерживает несколько языков (включая китайский и русский). Вы уже можете попробовать его, загрузив 3.4 Milestone и пройдя Учебное пособие по ArangoSearch.
Мы также работаем над значительным расширением и улучшением функциональности геоиндекса в ArangoDB 3.4. Он будет предлагать более продвинутые функции запросов, оптимизацию производительности и поддержку стандартных типов GeoJSON (многоугольники, мультиполигоны, полилинии, пересечения и т. д.).
Присоединяйтесь к нашему Сообществу Slack, чтобы оставлять нам отзывы, предложения по улучшению, если у вас есть какие-либо вопросы или просто общаться с нами.
Как ранее публиковалось на ArangoDB