EventNative – простой инструмент для записи потока событий в ClickHouse

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Данные стали бесценным активом, позволяющим компаниям лучше понимать своих пользователей, прогнозировать их поведение и определять тренды. EventNative – проект с открытым исходным кодом, разработанный командой из Jitsu, который позволяет упростить сбор данных о событиях. EventNative поддерживает работу с несколькими хранилищами данных, и ClickHouse – одно из них.

В этой статье мы расскажем как настроить EventNative с ClickHouse, а также в ней приводятся советы по эксплуатации и повышению производительности и надежности.

Загрузка данных в ClickHouse – не такая простая задача, как может показаться на первый взгляд. Обработка потоков с миллионами событий из различных приложений (где у каждого события может быть своя собственная структура) может вызывать значительные затруднения. Ситуация может стать особенно сложной, если в одной продакшн-среде работают разные версии одного и того же приложения (например, разные версии iOS-приложения).

Архитектура EventNative максимально эффективна и надежна. Она состоит из легковесного HTTP-сервера, принимающего поток событий (состоящий из JSON-объектов) и буферизующего его на локальный диск. Отдельный поток приложения занимается обработкой этого буфера, маппингом JSON-объектов с таблицами в ClickHouse, настройкой схемы и хранением данных.

Быстрый запуск ClickHouse и EventNative

В этом разделе мы расскажем об установке узла с ClickHouse и EventNative c помощью официальных Docker-образов.

Обратите внимание, что здесь мы рассматриваем настройку окружения для разработки. В продакшн-сценариях вы можете развернуть несколько узлов EventNative и подключить реплики ClickHouse, чтобы убедиться в доступности данных и масштабировать пропускную способность.

1. Скачиваем Docker-образы

docker pull ksense/eventnative:latest && docker pull yandex/clickhouse-server:latest

2. Запускаем ClickHouse

mkdir ./clickhouse_data && docker run --name clickhouse-test -p 8123:8123 -v $PWD/clickhouse_data:/var/lib/clickhouse yandex/clickhouse-server

3. Настраиваем EventNative

Добавьте следующий текст в ./eventnative.yaml

server:
 auth:
   - server_secret: 'ia7i92rqp3mh' # access token. We will need it later for sending events through HTTP API 
destinations:
  clickhouse:
    mode: stream
    clickhouse:
      dsns:
        - "http://default:@host.docker.internal:8123?read_timeout=5m&timeout=5m"
      db: default
    data_layout:
      mappings:
        fields:
        - src: /field_1/sub_field_1
          action: remove
        - src: /field_2/sub_field_1
          dst: /field_10/sub_field_1
          action: move
        - src: /field_3/sub_field_1/sub_sub_field_1
          dst: /field_20
          action: move
          type: DateTime
        - dst: /constant_field
          action: constant
          value: 1000

Также создайте директорию для логов: mkdir ./eventnative-logs

4. Запускаем EventNative

docker run -d -t --name eventnative-test -p 8001:8001 \
-v $PWD/eventnative.yaml:/home/eventnative/app/res/eventnative.yaml \
-v $PWD/eventnative-logs:/home/eventnative/logs/events/ ksense/eventnative:latest

5. Отправляем тестовое событие и проверяем, что оно записалось в ClickHouse

Добавьте следующий объект в ./api.json:

{
 "eventn_ctx": {
   "event_id": "19b9907d-e814-42d8-a16d-c5da51e01531"
 },
 "field_1":  {
   "sub_field_1": "text1",
   "sub_field_2": 100
 },
 "field_2": "text2",
 "field_3": {
   "sub_field_1": {
     "sub_sub_field_1": "2020-09-25T12:38:27"
   }
 }
}

Запустите следующую команду:

curl -X POST -H "Content-Type: application/json" -d @./api.json \
 'http://localhost:8001/api/v1/s2s/event?token=ia7i92rqp3mh' 
echo 'SELECT * FROM events;' | curl 'http://localhost:8123/' --data-binary @-

Вы увидите одно событие в базе данных. Тест сработал!

6. Тестируем буферизацию событий

Одна из основных фич EventNative'а — это буфферизация. События записываются во внутреннюю очередь с сохранением на диск. Если база данных (в нашем случае ClickHouse) недоступна, данные не будут потеряны. События будут сохранены локально и попадут в базу данных когда ClickHouse станет доступным опять.

Давайте протестируем эту фичу:

Добавьте следующий JSON в ./api2.json:

{
 "eventn_ctx": {
   "event_id": "4748c7bb-50d4-43a7-91b4-21a5bcccb12e"
 },
 "field_1":  {
   "sub_field_1": "text1",
   "sub_field_2": 100
 },
 "field_2": "text2",
 "field_3": {
   "sub_field_1": {
     "sub_sub_field_1": "2020-09-25T12:38:27"
   }
 }
}

Остановите ClickHouse

docker stop clickhouse-test

Отошлите событие

curl -X POST -H "Content-Type: application/json" -d @./api2.json 'http://localhost:8001/api/v1/s2s/event?token=ia7i92rqp3mh'

Проверьте что ClickHouse действительно не работает

echo 'SELECT * FROM events;' | curl 'http://localhost:8123/' --data-binary @-

Запустите ClickHouse опять

docker start clickhouse-test

Подождите 60 секунд и убедись, что событие не потерялось

echo 'SELECT * FROM events;' | curl 'http://localhost:8123/' --data-binary @-

Управление схемой с помощью EventNative и ClickHouse

EventNative спроектирован так, что вам не потребуется создавать схемы таблиц и поддерживать их. EventNative позаботится об этом автоматически! Каждое поле в JSON-объекте будет сопоставляться с полем в SQL. Если поле отсутствует, оно будет автоматически создано в базе ClickHouse.

Это особенно полезно, если одна команда разработчиков занимается структурой событий, а другая работает с ClickHouse. Приведем пример: фронтенд-разработчик может начать отправлять очень простые данные для отслеживания просмотров страницы продукта (id продукта и его стоимость), а затем будет добавлять более сложные поля (валюту и изображения). Потому что они могут пригодиться.

Пример:

JSON с событием

{
   "product_id":  "1e48fb70-ef12-4ea9-ab10-fd0b910c49ce",
   "product_price": 399.99,
   "price_currency": "USD",
   "product_type": "supplies",
   "product_release_start": "2020-09-25T12:38:27",
   "images": {
     "main": "picture1",
     "sub":  "picture2"
   }
}

Автоматически сгенерированная структура таблицы:

"product_id" String,
"product_price" Float64,
"price_currency" String,
"product_type" String,
"product_release_start" String,
"images_main" String,
"images_sub" String

По умолчанию, все поля не будут допускать запись Null. Проблема в том, что Nullable-поля влияют на производительность. Но в случае если Null значения необходимы - можно сконфигурировать список полей:

nullable_fields: ['product_id', 'product_price', 'price_currency']

(См полное описание конфигурации mapping'а)

Больше о конфигурация mapping'а

С помощью изменения настроек EventNative может применять к входящим JSON-объектам определенные преобразования, например:

  • Удалять поля

  • Переименовывать поля (включая перенос поля в другой узел)

  • Явно определять тип поля

  • Подставлять константу

Пример

  - src: /field_1/sub_field_1
    action: remove
  - src: /field_2/sub_field_1
    dst: /field_10/sub_field_1
    action: move
  - src: /field_3/sub_field_1/sub_sub_field_1
    dst: /field_20
    action: move
    type: DateTime
  - dst: /constant_field
    action: constant
    value: 1000

В результате применения правил следующий JSON

{
 "eventn_ctx": {
   "event_id": "19b9907d-e814-42d8-a16d-c5da51e01530" 
 },
 "field_1":  {
   "sub_field_1": "text1",
   "sub_field_2": 100
 },
 "field_2": "text2",
 "field_3": {
   "sub_field_1": {
     "sub_sub_field_1": "2020-09-25T12:38:27"
   }
 }
}

Превратится в такую запись в базе данных

{
 "eventn_ctx_eventn_id": "19b9907d-e814-42d8-a16d-c5da51e01530",
 "field_1_sub_field_1":  "text1",
 "field_1_sub_field_2":  100,
 "field_2": "text2",
 "field_3_sub_field_1_sub_sub_field_1": "2020-09-25T12:38:27.763000Z",
 "constant_field": 1000
}

Полное описание этой функции вы можете изучить в документации

Советы по повышению производительности

ReplacingMergeTree (или ReplicatedReplacingMergeTree) – лучший движок для данных, созданных EventNative, и вот почему:

  • Обычно данные, созданные EventNative используются в запросах с агрегатными функциями (например, при подсчете количества событий, удовлетворяющих некоторому условию, за заданный период времени). Движки из семейства MergeTree показывают отличную производительность при работе с такими запросами.

  • ReplacingMergeTree (в отличие от MergeTree) имеет приятный побочный эффект – дедупликация данных. Зачастую ошибки в данных обнаруживаются уже после их загрузки. Иногда их необходимо перезаписывать. Поскольку EventNative может хранить логи событий в течении некоторого времени, вы можете написать скрипт для исправления данных и их повторной записи. ReplacingMergeTree позволит избежать дублирования данных (при условии, что у каждого события есть уникальный идентификатор, который используется в качестве ключа)

Если целевая таблица отсутствует, EventNative создаст таблицу с помощью ReplacingMergeTree или ReplicatedReplacingMergeTree (если размер кластера больше 1). Однако, этот механизм можно настроить вручную. Подробнее о создании таблиц можно прочитать в документации

Источник: https://habr.com/ru/company/jitsu/blog/538326/


Интересные статьи

Интересные статьи

Статья о том, как упорядочить найм1. Информируем о вакансии2. Ведём до найма3. Автоматизируем скучное4. Оформляем и выводим на работу5. Отчитываемся по итогам6. Помогаем с адаптацией...
Введение Когда вы начинаете создавать репозиторий на GitHub, одной из первых вещей, о которых вы должны подумать, является безопасность. В случае, если вы создаете свой собственный репози...
По общению в поддержке и некоторым другим косвенным признакам мы заметили, что среди наших бесплатных пользователей стало много студентов. Причина: анализатор PVS-Studio начал использоваться не...
Илон Маск рассказывает о Neuralink Когда Илон Маск впервые рассказал о нейромозговом интерфейсе Neuralink, новость об этом разошлась по всему миру за считанные часы. Чуть позже он заявил, чт...
Обычно pytest используется не самостоятельно, а в среде тестирования с другими инструментами. В этой главе рассматриваются другие инструменты, которые часто используются в сочетании с pytest для эффек...