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

Как мы видели в моей предыдущей статье, Elasticseach на самом деле не поддерживает обновления. В Elasticsearch обновление всегда означает удаление + создание.

В предыдущем проекте мы использовали Elasticsearch для полнотекстового поиска, и нам нужно было сохранить некоторые сигналы, такие как новые подписчики, вместе с пользовательским документом.

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

Наивное решение - просто выпускать эти обновления - хороший способ поджечь кластер Elasticsearch :)

У нас была терпимость к возможной согласованности этой системы, поэтому мы могли жить с задержкой поступления этих сигналов в Elasticsearch.

При такой гибкости окончательное решение заключалось в преобразовании всех изменений, которые необходимо было сохранить, в события с использованием шаблона проектирования Event Sourcing. Состояние приложения в этом случае будет храниться в Elasticsearch.

Вторая часть решения - как сохранить это состояние.

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

Это решение основано на концепции потоков. Я использую Kafka, но Redis Streams или AWS Kinesis будут работать одинаково.

Идея состоит в том, чтобы сохранить все новые изменения (например, нового подписчика, изменение имени пользователя и т. Д.) В разделенной теме. Убедитесь, что ваш ключ раздела соответствует идентификатору документа, чтобы гарантировать порядок, но также избегайте одного раздела для каждого идентификатора пользователя, иначе вы убьете свой кластер Kafka.

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

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

Использование потоков может быть препятствием, если вы еще не приняли его, поэтому я решил включить пример и с Redis (не с потоками Redis).

Redis имеет множество структур данных, как мы видели в этой статье, а это значит, что у нас есть множество правильных способов выполнить эту работу.

Один из способов - использовать атомарные операции RPUSH и LPOP для выполнения работы.

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

Например, предположим, что первый и второй пакеты содержат изменение имени пользователя для одного и того же пользователя, и они обрабатываются двумя разными рабочими. Вы теряете любые гарантии заказа.

Чтобы преодолеть это, нам нужно следовать шаблону разделения, аналогичному тому, который мы использовали для нашего решения Kafka, но в этом случае с использованием нескольких LIST (Redis Keys) и назначением одного рабочего для каждого списка.

Дальнейшее чтение



Как все это звучит? Есть ли что-нибудь, о чем я хотел бы рассказать?

Дайте мне знать свои мысли в разделе комментариев ниже (и хлопайте в ладоши, если это было полезно)!

Следите за следующей публикацией. Подписывайтесь, чтобы не пропустить!