⚡️Ускорение индексации денормализованных документов



В посте про подходы к поиску иерархичных данных был небольшой рассказ про денормализацию — это когда есть родительский документ { ticket } и есть дочерние { article_1 }, { article_2 }, { article_3 }. И далее мы уплощаем эту структуру в три документа, где child-ы обогащены данными от родителя



Псевдокод:

{ ticket, article_1 },

{ ticket, article_2 },

{ ticket, article_3 }




Такой подход позволяет добиться хорошего перфоманса при поиске (поскольку все необходимые данные находятся в рамках одного документа), но теперь нужно на каждое изменение родительского документа переиндексировать всех его детей



Какие есть способы ускорить такую индексацию в elasticsearch:



0. Наивный неэффективный способ



Получили эвент обновления ticket => запрашиваем все артиклы => строим n документов для обновления артиклов => делаем n запросов в эластик



Если у тикета 500 артиклов, то к примеру при изменении статуса тикета, нам нужно будет сделать 500 запросов на переиндексацию — звучит крайне неэффективно



Псевдокод:

query 1: { ticket: { all fields }, article { all fields }

query 2: { ticket: { all fields }, article { all fields }

query 3: { ticket: { all fields }, article { all fields }

...




1. Использование bulk update + partial update



Нам приходится делать кучу запросов, мы это можем решить с помощью использования bulk update, передав в одном запросе список того, что хотим переиндексировать



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



Псевдокод:

query: [

{ article_id: 1, ticket: { changed fields } }

{ article_id: 2, ticket: { changed fields } }

{ article_id: 3, ticket: { changed fields } }

...

]




Важное НО: эластик делает shallow partial update, т.е. вложенные объекты обновляются целиком сразу, а не частично. К примеру, если бы мы захотели обновить вложенное поле ticket.custom_fields.some_field, то нам мы пришлось полностью передать объект ticket. В противном случае мы бы потеряли данные



2. Использование update_by_query + script



Нивелируем проблемы предыдущего решения, а именно: shallow апдейты и то, что мы можем заранее не знать id-шники всех дочерних документов



Для этой цели есть update_by_query, который позволяет обновить документы, которые соответствуют некоторому условию. И обновить их с помощью скрипта



Псевдокод:

query: {

condition: ticket_id = 123,

script: ticket.custom_fields.some_field = "abc"

}




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