Статьи

Что нового в RavenDB 3.0: улучшения индексации

 

шахматная 345904_640

Ранее мы говорили о том, что мы улучшили в RavenDB 3.0 для серверной части индексирования. В этом посте я хочу рассказать о нескольких функциях, которые гораздо более заметны.

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

Оптимизировано создание нового индекса. В RavenDB 2.5 создание нового индекса заставит нас просмотреть все документы в базе данных, а не только документы, которые есть в этой коллекции. Во многих случаях это удивляло пользователей, потому что они ожидали какого-то физического разделения между коллекциями. В RavenDB 3.0 мы изменили положение вещей, поэтому создание нового индекса для небольшой коллекции (по умолчанию менее 131 072 элементов) сможет касаться только тех документов, которые относятся к коллекциям, охватываемым этим индексом. Уже одно это представляет собой довольно существенное изменение в способе обработки индексов.

На практике это означает, что создание нового индекса для небольшого индекса завершится гораздо быстрее. Например, я сбрасываю индекс для производственного экземпляра, он охватывает около 7 583 документов, из которых 19 191. RavenDB смог проиндексировать это всего за 690 мс, из примерно 3 секунд в общем, что потребовалось для сброса индекса.

Как насчет случаев, когда у нас есть новые индексы для больших коллекций? На этом этапе, в 2.5, мы выполняем циклическую индексацию между новым и существующим индексами. Проблема заключалась в том, что 2,5 был смещен в сторону нового индекса. Это означало, что он был занят индексированием нового материала, в то время как существующие индексы (которые вы фактически используете) выполнялись дольше. Другая проблема заключалась в том, что в версии 2.5 создание нового индекса эффективно отравляло бы много эвристик производительности. Они были построены для допущений всех индексов, работающих в основном в тандеме. И когда у нас есть один или несколько, которые не делали этого… ну, это делало вещи более дорогими.

В 3.0 мы изменили, как это работает. У нас будут отдельные конвейеры оптимизации производительности для каждой группы индексов в зависимости от ее приблизительной позиции индексации. Это позволяет нам использовать преимущества объединения множества индексов. Мы также не будем пытаться чередовать индексы (запустив сначала новый индекс, а затем уже существующие). Вместо этого мы будем запускать их все параллельно, чтобы сократить количество остановок и увеличить скорость, с которой все идет к скорости.

Это использует наш механизм планирования, чтобы гарантировать, что мы фактически не перегружаем машину вычислительной работой (одновременное индексирование) или памятью (количество элементов для индексации одновременно). Я очень горжусь тем, что мы сделали здесь, и хотя это на самом деле функция бэкэнда, слишком важно потеряться в мелочах всех других изменений индексации бэкэнда, о которых мы говорили в моем предыдущем посте.

Явная декартова / разветвленная индексация. Декартовой индекс (мы обычно называем их индексами разветвления) — это индекс, который выводит несколько записей индекса для каждого документа. Вот пример такого индекса:

from postComment in docs.PostComments
from comment in postComment.Comments
where comment.IsSpam == false
select new {
    CreatedAt = comment.CreatedAt,
    CommentId = comment.Id,
    PostCommentsId = postComment.__document_id,
    PostId = postComment.Post.Id,
    PostPublishAt = postComment.Post.PublishAt
}

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

Но здесь есть проблема. RavenDB не может заранее знать, сколько записей индекса будет сгенерировано документом, это означает, что очень трудно выделить для этого соответствующий объем памяти, и возможно попасть в ситуации, когда у нас просто заканчивается память. В RavenDB 3.0 мы добавили явные инструкции для этого. Индекс имеет бюджет, по умолчанию каждому документу разрешено выводить до 15 записей. Если он попытается вывести более 15 записей, индексация этого документа будет прервана, и этот индекс не будет проиндексирован.

Вы можете переопределить эту опцию либо глобально, либо на основе индекса по индексу, чтобы увеличить число записей индекса на документ, которые разрешены для индекса (а старые индексы будут иметь ограничение в 16 384 элемента, чтобы избежать нарушения существующих индексов).

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

Упрощенные авто индексы. Эта особенность тесно связана с предыдущей. Допустим, мы хотим найти всех пользователей, у которых есть роль администратора и у которых не просрочена кредитная карта. Мы делаем это, используя следующий запрос:

var q = from u in session.Query<User>()
        where u.Roles.Any(x=>x.Name == "Admin") && u.CreditCards.Any(x=>x.Expired == false)
        select u;

В RavenDB 2.5 мы сгенерировали бы следующий индекс для ответа на этот запрос:

from doc in docs.Users
from docCreditCardsItem in ((IEnumerable<dynamic>)doc.CreditCards).DefaultIfEmpty()
from docRolesItem in ((IEnumerable<dynamic>)doc.Roles).DefaultIfEmpty()
select new {
    CreditCards_Expired = docCreditCardsItem.Expired,
    Roles_Name = docRolesItem.Name
}

И в RavenDB 3.0 мы генерируем это:

from doc in docs.Users
select new {
    CreditCards_Expired = (
        from docCreditCardsItem in ((IEnumerable<dynamic>)doc.CreditCards).DefaultIfEmpty()
        select docCreditCardsItem.Expired).ToArray(),
    Roles_Name = (
        from docRolesItem in ((IEnumerable<dynamic>)doc.Roles).DefaultIfEmpty()
        select docRolesItem.Name).ToArray()
}

Обратите внимание на разницу между ними. 2.5 будет генерировать несколько записей индекса для каждого документа, а RavenDB 3.0 — только одну. Хуже всего то, что 2.5 будет генерировать декартово произведение, поэтому число записей индекса, выведенных в 2.5, будет числом ролей для пользователя, умноженным на количество кредитных карт, которые у него есть. В RavenDB 3.0 у нас всего одна запись, и общая стоимость значительно снижена. Это было большое изменение, но я думаю, оно того стоило, учитывая альтернативу.

В моем следующем посте я расскажу о другой стороне индексации — запросах. Держись, нам еще многое предстоит пережить.