Русский
Русский
English
Статистика
Реклама

Timeseries

Перевод Прогнозирование временных рядов на JS анализ данных для самых маленьких фронтендеров

10.06.2021 16:07:13 | Автор: admin

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

В чем суть?

На своей работе в качестве React Front-end разработчика я обычно работаю с дашбордами и различными видами данных. В какой-то момент нам понадобилось добавить предсказания по метрикам, а в команде не было специалистов по анализу данных, которые могли бы этим заняться.

Наш стек - это React + Java.

Проблема 1

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

Проблема 2

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

Поэтому мы решили сделать предсказание рядов на стороне клиента - в браузере. Мы ж фронтендеры!

Проверим, что ряды вообще можно предсказать

Для этого загоним данные в эксель и посмотрим на результаты функции FORECAST.ETS(). Наши сезонные прогнозы выглядят правдоподобно. Мы проверили, что на наших данных реально получить что-то адекватное, поэтому можно теперь искать JS-либы для предсказаний!

Прогнозы рядов на JS

Если решились делать предсказания на фронте (и экономить время бекендеров), то нужно найти что-то готовенькое, а не делать предсказания с нуля.

Я экспериментировал с моделью Tensorflow.js RNN из этой статьи, но она требует много времени для обучения на заданном наборе данных, сам набор данных должен быть достаточно большим, предсказание тоже не быстрое. Короче, нам она не подошла: у нас 1000+ рядов из 40-50 записей в каждом.

Быстро найти норм реализацию ARIMA в JS не удалось, зато нашли либу Nostradamus, где реализован алгоритм экспоненциациального сглаживания Холта-Уинтерса.

Найденная либа работает достаточно удобно:

predict = (    data,    a = 0.95,    b = 0.4,    g = 0.2,    p = this.PERIODS_TO_PREDICT,  ) => {    const alpha = a;    const beta = b;    const gamma = g;    const predictions = forecast(data, alpha, beta, gamma, this.OBSERVATIONS_PER_SEASON, p);    return predictions;  };

Функция Forecast возвращает массив элементов, где последние p элементов являются предсказанными значениями. Чисто и просто.

Но это не конец

Было бы как-то очень слабо закончить статью на этом месте. Добавлю подводные камни, которые замедлили интеграцию client-side предсказаний в проект:

  1. У этого алгоритма есть ограничение, которое может оказаться довольно весомым: мы не можем прогнозировать дальше, чем на количество элементов в одном сезоне. То есть если, к примеру, мы прогнозируем продажи книг по месяцам год к году, то в таком случае мы не можем предсказывать дальше, чем на 12 месяцев.

  2. Помимо пункта 1, у нас есть еще один лимит - у нас должно быть по меньшей мере 2 полных сезона с данными. Если взять тот же пример с книгами, то мы должны знать количество проданных книг в месяц за последние 24 месяца (2 года).

  3. Иногда бывает так, что в рамках проекта мы предсказываем разные метрики, которые, очевидно, друг с другом не связаны. А это значит, что коэффиценты (альфа/гамма/бета) от одной метрики не подойдут к другой и нам надо вычислять их динамически. В этом случае мы вычисляем значение ошибки для разных показателей и в конце выбираем набор с наименьшей ошибкой (сниппет с примером такого вычисления будет как бонус в конце статьи). Очевидно, что это влияет на производительность, но в нашем случае это было незначительно.

  4. Нам нужно такое количество записей, чтобы оно нацело делилось на размер сезона. Если сезон - это год, и в нем 12 записей (месяцев), то для прогноза нужно брать, например, 24/36/48 записей.

И еще одно: я не понял, в чем дело, но имея один набор исторических данных и разное количество записей, которые мы собираемся предсказать (например, есть история за 2 года, а предсказать хотим то на 3 месяца вперед, то на 12), мы получим разные прогнозы. Нам нужно было считать на 3 месяца вперед, поэтому я сделал еще один лайфхак - считал ошибку для обоих случаев и выбирал тот, в котором ошибка меньше.

Синяя линия - текущий гоод. Фиолетовая - предыдущий. Пунктир - прогноз.Синяя линия - текущий гоод. Фиолетовая - предыдущий. Пунктир - прогноз.Прогнозирование по трем независимым метрикам (пунктирные линии). Синяя линия - текущий год.Прогнозирование по трем независимым метрикам (пунктирные линии). Синяя линия - текущий год.

Бонус - код для расчета ошибок и подгона параметров

const adjustParams = (period) => {      const iter = 10;      const incr = 1 / iter;      let bestAlpha = 0.0;      let bestError = -1;      let alpha = bestAlpha;      let bestGamma = 0.0;      let gamma = bestGamma;      let bestDelta = 0.0;      let delta = bestDelta;      while (alpha < 1) {        while (gamma < 1) {          while (delta < 1) {            const pred = this.predict(data, alpha, delta, gamma, period);            const error = this.computeMeanSquaredError(data, pred);            if (error < bestError || bestError === -1) {              bestAlpha = alpha;              bestGamma = gamma;              bestDelta = delta;              bestError = error;            }            delta += incr;          }          delta = 0;          gamma += incr;        }        gamma = 0;        alpha += incr;      }      alpha = bestAlpha;      gamma = bestGamma;      delta = bestDelta;      return {        alpha,        gamma,        delta,        bestError,      };    };

Бонус 2

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


А какие вы задачи решали на стороне клиента? Напишите свою историю в комментариях!
Перевел: Даниил Охлопков.

Подробнее..

Cortex и не только распределённый Prometheus

04.03.2021 10:11:49 | Автор: admin

В последнее время Prometheus стал де-факто стандартом для сбора и хранения метрик. Он удобен для разработчиков ПО - экспорт метрик можно реализовать в несколько строк кода. Для DevOps/SRE, в свою очередь, есть простой язык PromQL для получения метрик из хранилища и их визуализации в той же Grafana.

Но Prometheus имеет ряд недостатков, способы устранения которых я хочу рассмотреть в этой статье. Также разберём деплой Cortex - распределённого хранилища метрик.

Недостатки

  • Отсутствие отказоустойчивости

    Prometheus работает только в единственном экземпляре, никакого HA.

  • Отсутствие распределения нагрузки

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

  • Нет поддержки multi-tenancy

    Все метрики летят в один большой котёл и разгребать их потом используя PromQL и метки не всегда удобно. Часто хочется разделить различные приложения и\или команды по своим песочницам чтобы они друг другу не мешали.

    Плюс, многие готовые дашборды для Grafana не готовы к тому что много инстансов одного приложения хранят метрики в одном и том же месте - придётся переделывать все запросы, добавлять фильтры по меткам и так далее.

В принципе, все эти проблемы можно решить настроив несколько HA-пар Prometheus и распределив по ним свои приложения. Если перед каждой парой повесить прокси, то можно получить что-то вроде отказоустойчивости.

Но есть и минусы:

  • После того как один хост из пары упадёт/перезагрузится/whatever - у них случится рассинхронизация. В метриках будут пропуски.

  • Все метрики приложения должны умещаться на один хост

  • Управлять таким зоопарком будет сложнее - какие-то из Prometheus могут быть недогружены, какие-то перегружены. В случае запуска в каком-нибудь Kubernetes это не так важно.

Давайте рассмотрим какими ещё способами можно решить это.

PromQL прокси

Например promxy, который размещается перед 2 или более инстансами Prometheus и делает fan-out входящих запросов на все из них. Затем дедуплицирует полученные метрики и, таким образом, закрывает пропуски в метриках (если, конечно, они не попали на один и тот же временной интервал).

Минусы подобного решения на поверхности:

  • Один запрос нагружает сразу все инстансы за прокси

  • Прокси решает только проблему с пропусками в метриках.

Но для тех, у кого нагрузка укладывается в возможности одного Prometheus (либо ее можно грамотно раскидать по нескольким HA-парам) и кому не нужен multi-tenancy - это очень хороший вариант.

Thanos

Thanos - это уже более продвинутое решение.

Он устанавливает рядом с каждым инстансом Prometheus так называемый Sidecar - отдельный демон, который подглядывает за блоками данных, которые генерирует Prometheus. Как только блок закрывается - Sidecar загружает его в объектное хранилище (S3/GCS/Azure). Длина блоков в Prometheus прибита гвоздями и равна 2 часам.

Также он является прокси между GRPC Thanos StoreAPI и Prometheus для получения метрик, которые еще не были загружены в объектное хранилище.

Отдельный компонент Querier реализует PromQL: в зависимости от временного интервала запроса и настроек глубины хранения данных в Prometheus он может направить его в объектное хранилище, в Sidecar или в разбить на два подзапроса - для свежих данных запрос пойдёт через Sidecar в Prometheus, а для более старых - в объектное хранилище.

Отказоустойчивость свежих данных в Thanos реализуется примерно так же как и в promxy - делается fan-out запросов на все причастные сервера, результаты накладываются друг на друга и дедуплицируются. Задача по защите исторических данных лежит на объектном хранилище.

Multi-tenancy есть в некотором зачаточном состоянии, но в эту сторону проект, судя по всему, не развивается особо.

Cortex

Это наиболее сложный и функциональный проект. Его начали разрабатывать в Grafana Labs для своего SaaS решения по хранению метрик и несколько лет назад выложили в open source, с тех пор разработка идёт на гитхабе.

Как можно видеть на диаграмме выше - в нём очень много компонентов. Но бояться не стоит - большую часть из них можно не использовать, либо запускать в рамках одного процесса - single binary mode.

Так как Cortex изначально разрабатывался как SaaS решение - в нём поддерживается end-to-end multi-tenancy.

Хранение метрик

На данный момент в Cortex есть два движка. Оба они хранят данные в объектном хранилище, среди которых поддерживаются:

  • S3

  • GCS

  • Azure

  • OpenStack Swift (экспериментально)

  • Любая примонтированная ФС (например - NFS или GlusterFS). Хранить блоки на локальной ФС смысла нет т.к. они должны быть доступны всему кластеру.

Далее я буду для краткости называть объектное хранилище просто S3.

Chunks Storage

Изначальный движок в Cortex - он хранит каждую timeseries в отдельном чанке в S3 или в NoSQL (Cassandra/Amazon DynamoDB/Google BigTable), а метаданные (индексы) хранятся в NoSQL.

Chunks Storage, думается, со временем совсем выпилят - насколько я слышал, Grafana Labs свои метрики уже мигрировали в Blocks Storage.

Blocks Storage

Новый, более простой и быстрый движок, основанный на Thanos. Который, в свою очередь, использует формат блоков самого Prometheus. С ним нет нужды в NoSQL и модуле Table Manager (но нужен другой - Store Gateway).

Thanos, в данном, случае является внешней vendored зависимостью в коде Cortex. Есть разговоры о том, чтобы объединить два проекта в один, но когда это будет неизвестно (и будет ли вообще).

Архитектура

Далее я буду рассматривать работу с Blocks Storage.

Упрощённо принцип работы следующий:

  • Prometheus собирает метрики с endpoint-ов и периодически отправляет их в Cortex используя Remote Write протокол. По сути это HTTP POST с телом в виде сериализованных в Protocol Buffers метрик сжатый потом Snappy. В самом Prometheus, при этом, можно поставить минимальный retention period - например 1 день или меньше- читаться из него ничего не будет.

  • Модуль Distributor внутри Cortex принимает, валидирует, проверяет per-tenant и глобальные лимиты и опционально шардит пришедшие метрики. Далее он передает их одному или нескольким Ingester (в зависимости от того применяется ли шардинг).

    Также в рамках этого модуля работает HA Tracker (о нём ниже).

  • Ingester ответственен за запись метрик в долговременное хранилище и выдачу их для выполнения запросов. Изначально метрики записываются в локальную ФС в виде блоков длиной 2 часа. Затем, по истечении некоторого времени, они загружаются в S3 и удаляются с локальной ФС.

    Также поддерживается репликация и zone awareness для дублирования блоков по различным availability domain (стойки, ДЦ, AWS AZ и так далее)

  • Store-Gateway служит для отдачи блоков из S3.

    Он периодически сканирует бакет, находит там новые блоки, синхронизирует их заголовки в локальную ФС (чтобы после перезапуска не скачивать опять) и индексирует.

  • Querier реализует PromQL.

    При получении запроса анализирует его и, если необходимо, разбивает на два - одна часть пойдёт в Store Gateway (для более старых данных), а другая - в Ingester для свежих.

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

  • Compactor периодически просыпается и сканирует объектное хранилище на предмет блоков, которые можно склеить в более крупные. Это приводит к более эффективному хранению и быстрым запросам.

    Старые блоки не удаляются сразу, а маркируются и удаляются на следующих итерациях чтобы дать время Store-Gateway обнаружить новые, которые уже сжаты.

Отказоустойчивость

Помимо репликации данных между Ingester-ами нам необходимо обеспечить отказоустойчивость самих Prometheus. В Cortex это реализовано просто и элегантно:

  • Два (или более) Prometheus настраиваются на сбор метрик с одних и тех же endpoint-ов

  • В каждом из них настраиваются специальные внешние метки, которые показывают к какой HA-группе принадлежит данный Prometheus и какой у него идентификатор внутри группы.

    Например так:

  external_labels:    __ha_group__: group_1    __ha_replica__: replica_2
  • При приёме метрик Cortex из каждой группы выбирает один Prometheus и сохраняет метрики только от него

  • Остальным отвечает HTTP 202 Accepted и отправляет в /dev/null всё что они прислали

  • Если же активный инстанс перестал присылать метрики (сработал таймаут) - Cortex переключается на приём от кого-то из оставшихся в живых.

Таким образом дедупликация на этом уровне становится не нужна. Правда остаётся вопрос момента переключения - не теряется ли там что-либо (когда старый не прислал, а от нового ещё не приняли), я этот вопрос не изучал глубоко - нужно тестировать.

Авторизация

Каждый запрос на запись метрик из Prometheus должен содержать HTTP-заголовок X-Scope-OrgId равный идентификатору клиента (далее я буду называть их просто tenant, хорошего перевода не придумал). Метрики каждого tenant-а полностью изолированны друг от друга - они хранятся в разных директориях в S3 бакете и на локальной ФС

Таким же образом происходит и чтение метрик - в PromQL запросах тоже нужно тоже указывать этот заголовок.

При этом никакой реальной авторизации Cortex не проводит - он слепо доверяет этому заголовку. Auth Gateway есть в роадмапе, но когда он будет готов неизвестно. Даже просто добавить этот заголовок напрямую в Prometheus нельзя, только используя промежуточный HTTP прокси.

Для более гибкой интеграции Prometheus & Cortex я набросал простенький Remote Write прокси - cortex-tenant, который может вытаскивать ID клиента из меток Prometheus. Это позволяет использовать один инстанс (или HA-группу) Prometheus для отправки метрик нескольким разным клиентам. Мы используем этот функционал для разграничения данных разных команд, приложений и сред.

Авторизацию можно отключить, тогда Cortex не будет проверять наличие заголовка в запросах и будет подразумевать что он всегда равен fake - то есть multi-tenancy будет отключен, все метрики будут падать в один котёл.

При необходимости данные одного клиента можно полностью удалить из кластера - пока это API экспериментально, но работает.

Настройка Cortex

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

Для всех остальных гораздо проще установить несколько HA-пар Prometheus (например на каждую команду или каждый проект) и поверх них натянуть promxy

Так как документация имеет некоторое количество белых пятен - я хочу рассмотреть настройку простого кластера Cortex в режиме single binary - все модули у нас будут работать в рамках одного и того же процесса.

Danger Zone! Дальше много конфигов!

Зависимости

Нам понадобится ряд внешних сервисов для работы.

  • etcd для согласования кластера и хранения Hash Ring

    Cortex также поддерживает Consul и Gossip-протокол, которому не нужно внешнее KV-хранилище. Но для HA-трекера Gossip не поддерживается из-за больших задержек при сходимости. Так что будем юзать etcd

  • memcached для кеширования всего и вся.

    Cortex поддерживает его в нескольких своих модулях. Можно поднять несколько инстансов и указать их все в Cortex - он будет шардить по всем равномерно. В принципе, он не обязателен, но крайне рекомендован с точки зрения производительности.

    Также есть DNS-based discovery через SRV-записи, если не хочется указывать вручную.

  • minio для реализации распределённого S3 хранилища.

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

    Но других вариантов особо нет, можно поднять Ceph с S3 шлюзом, но это еще более громоздко.

    minio поддерживает Erasure Coding для отказоустойчивости, что есть хорошо.

  • HAProxy для связывания компонентов воедино

  • cortex-tenant для распределения метрик по tenant-ам

  • Prometheus собственно для сбора метрик

Общие вводные

  • Кластер мы будем строить плоский из 4 хостов - все они будут идентичны, с одинаковым набором сервисов. Это хорошо для небольших инсталляций, упрощает структуру.

    3 страйпа не поддерживает minio c Erasure Coding - он нарезает от 4 до 16 дисков в один EC-набор. В реальном проекте лучше использовать 5 или какое-либо большее нечетное число чтобы не было Split Brain.

    Также, если у вас много хостов, то некоторые компоненты, такие как etcd, лучше вынести отдельно. etcd основан на Raft и реплицирует данные на все ноды кластера, смысла в их большом количестве нет - это только увеличит среднюю нагрузку лишними репликами.

  • Все данные будем хранить в /data

  • Конфиги я буду приводить для одного хоста, для остальных обычно достаточно поменять адреса и\или хостнеймы

  • В качестве ОС используем RHEL7, но различия с другими дистрибутивами минимальны

  • У нас всё это дело, конечно, раскатывается через Ansible, но плейбук довольно сильно завязан на нашу инфраструктуру. Я постараюсь потом его подчистить и выложить

  • Некоторые RPM пакеты я собираю вручную (etcd, HAProxy и т.п.) с помощью FPM т.к. в репозиториях древние версии.

/etc/hosts
10.0.0.1 ctx110.0.0.2 ctx210.0.0.3 ctx310.0.0.4 ctx4

etcd

Как и Zookeeper, с настройками по умолчанию etcd - бомба замедленного действия. Он не удаляет ненужные снапшоты и разрастается до бесконечности. Зачем так сделано - мне не понятно.

Поэтому настроим его соответственно:

/etc/etcd/etcd.conf
ETCD_NAME="ctx1"ETCD_LOGGER="zap"ETCD_LOG_LEVEL="warn"ETCD_DATA_DIR="/data/etcd/ctx1.etcd"ETCD_LISTEN_CLIENT_URLS="http://personeltest.ru/away/10.0.0.1:2379,http://127.0.0.1:2379"ETCD_LISTEN_PEER_URLS="http://personeltest.ru/away/10.0.0.1:2380"ETCD_ADVERTISE_CLIENT_URLS="http://personeltest.ru/away/10.0.0.1:2379"ETCD_INITIAL_CLUSTER_TOKEN="cortex"ETCD_INITIAL_ADVERTISE_PEER_URLS="http://personeltest.ru/away/10.0.0.1:2380"ETCD_AUTO_COMPACTION_RETENTION="30m"ETCD_AUTO_COMPACTION_MODE="periodic"ETCD_SNAPSHOT_COUNT="10000"ETCD_MAX_SNAPSHOTS="5"ETCD_INITIAL_CLUSTER="ctx1=http://ctx1:2380,ctx2=http://ctx2:2380,ctx3=http://ctx3:2380,ctx4=http://ctx4:2380"

memcached

Тут всё просто, главное выделить нужное количество памяти. Она зависит от количества метрик, которые мы будем хранить. Чем больше уникальных комбинаций меток - тем больше нужно кеша.

/etc/sysconfig/memcached
PORT="11211"USER="memcached"MAXCONN="512"CACHESIZE="2048"OPTIONS="--lock-memory --threads=8 --max-item-size=64m"

Minio

Тут минимум настроек.

По сути мы просто перечисляем хосты, которые будут использоваться для хранения данных (+ путь до директории где данные хранить - /data/minio) и указываем ключи S3. В моем случае это были ВМ с одним диском, если у вас их несколько - то формат URL несколько меняется.

По умолчанию используется странное распределение дисков под данные и под коды Рида-Соломона: половина сырого объема уходит под redundancy. Так как у нас всего 4 хоста - это не особо важно. Но на большем по размеру кластере лучше использовать Storage Classes для снижения доли Parity-дисков.

/etc/minio/minio.env
MINIO_ACCESS_KEY="foo"MINIO_SECRET_KEY="bar"MINIO_PROMETHEUS_AUTH_TYPE="public"LISTEN="0.0.0.0:9000"ARGS="http://personeltest.ru/away/ctx{1...4}/data/minio"

Также нужно будет создать бакет с помощью minio-client - в нашем случае пусть называется cortex

HAProxy

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

Таким образом мы имеем что-то вроде Full Mesh топологии и отказ или перезапуск любого из сервисов или хостов целиком не влияет на функциональность кластера.

На больших кластерах (сотни-тысячи хостов) такая схема может быть узким местом, но если вы работаете с такими, то и сами это знаете :)

/etc/haproxy/haproxy.cfg
global    daemon    maxconn 10000    log 127.0.0.1 local2    chroot /var/emptydefaults    mode http    http-reuse safe    hash-type map-based sdbm avalanche    balance roundrobin    retries 3    retry-on all-retryable-errors    timeout connect 2s    timeout client 300s    timeout server 300s    timeout http-request 300s    option splice-auto    option dontlog-normal    option dontlognull    option forwardfor    option http-ignore-probes    option http-keep-alive    option redispatch 1    option srvtcpka    option tcp-smart-accept    option tcp-smart-connect    option allbackupslisten stats    bind 0.0.0.0:6666    http-request use-service prometheus-exporter if { path /metrics }    stats enable    stats refresh 30s    stats show-node    stats uri /frontend fe_cortex    bind 0.0.0.0:8090 tfo    default_backend be_cortexfrontend fe_cortex_tenant    bind 0.0.0.0:8009 tfo    default_backend be_cortex_tenantfrontend fe_minio    bind 0.0.0.0:9001 tfo    default_backend be_miniobackend be_cortex    option httpchk GET /ready    http-check expect rstring ^ready    server ctx1 10.0.0.1:9009 check observe layer7 inter 5s    server ctx2 10.0.0.2:9009 check observe layer7 inter 5s    server ctx3 10.0.0.3:9009 check observe layer7 inter 5s    server ctx4 10.0.0.4:9009 check observe layer7 inter 5sbackend be_cortex_tenant    option httpchk GET /alive    http-check expect status 200    server ctx1 10.0.0.1:8008 check observe layer7 inter 5s    server ctx2 10.0.0.2:8008 check observe layer7 inter 5s backup    server ctx3 10.0.0.3:8008 check observe layer7 inter 5s backup    server ctx4 10.0.0.4:8008 check observe layer7 inter 5s backupbackend be_minio    balance leastconn    option httpchk GET /minio/health/live    http-check expect status 200    server ctx1 10.0.0.1:9000 check observe layer7 inter 5s    server ctx2 10.0.0.2:9000 check observe layer7 inter 5s backup    server ctx3 10.0.0.3:9000 check observe layer7 inter 5s backup    server ctx4 10.0.0.4:9000 check observe layer7 inter 5s backup

cortex-tenant

Это просто прокси между Prometheus и Cortex. Главное - выбрать уникальное имя метки для хранения там tenant ID. В нашем случае это ctx_tenant

/etc/cortex-tenant.yml
listen: 0.0.0.0:8008target: http://127.0.0.1:8090/api/v1/pushlog_level: warntimeout: 10stimeout_shutdown: 10stenant:  label: ctx_tenant  label_remove: true  header: X-Scope-OrgID

Prometheus

В случае 4 хостов Prometheus-ы можно разбить их на две HA-пары, каждую со своим ID группы и раскидать job-ы по ним.

host1 /etc/prometheus/prometheus.yml
global:  scrape_interval: 60s  scrape_timeout: 5s  external_labels:    __ha_group__: group_1    __ha_replica__: replica_1remote_write:  - name: cortex_tenant    url: http://127.0.0.1:8080/pushscrape_configs:  - job_name: job1    scrape_interval: 60s    static_configs:      - targets:          - ctx1:9090        labels:          ctx_tenant: foobar  - job_name: job2    scrape_interval: 60s    static_configs:      - targets:          - ctx2:9090        labels:          ctx_tenant: deadbeef
host2 /etc/prometheus/prometheus.yml
global:  scrape_interval: 60s  scrape_timeout: 5s  external_labels:    __ha_group__: group_1    __ha_replica__: replica_2remote_write:  - name: cortex_tenant    url: http://127.0.0.1:8080/pushscrape_configs:  - job_name: job1    scrape_interval: 60s    static_configs:      - targets:          - ctx1:9090        labels:          ctx_tenant: foobar  - job_name: job2    scrape_interval: 60s    static_configs:      - targets:          - ctx2:9090        labels:          ctx_tenant: deadbeef

По сути конфигурации в пределах одной HA-группы должны отличаться только лейблом реплики.

Cortex

Ну и последнее. Так как мы будем запускать все модули вместе - конфиг получится довольно объемный. Поэтому разделим его на части, чтобы читабельнее было.

Многие модули, такие как Distributor, Ingester, Compactor, Ruler кластеризуются с помощью Hash-Ring в etcd. На весь кластер выделяется некоторое количество токенов, которые распределяются между всеми участниками кольца равномерно.

Упрощенно - при приходе, допустим, новой метрики её метки хешируются, результат делится по модулю на количество токенов и в итоге направляется на хост, который владеет данным диапазоном токенов. Если он не отвечает - то отправляют следующему по кольцу хосту.

Если хост выходит из кластера (помер, перезагружается и т.п.), то его диапазон перераспределяется по остальным.

Все настройки у нас будут лежать в /etc/cortex/cortex.yml

Также т.к. Cortex сделан для работы в контейнерах - всё можно настроить через командную строку чтобы не пропихивать конфиг в контейнер.

Глобальные настройки

# Список модулей для загрузкиtarget: all,compactor,ruler,alertmanager# Требовать ли заголовок X-Scope-OrgIdauth_enabled: true# Портыserver:  http_listen_port: 9009  grpc_listen_port: 9095limits:  # Разрешаем HA-трекинг  accept_ha_samples: true  # Названия меток, которые мы используем в Prometheus для  # маркировки групп и реплик  ha_cluster_label: __ha_group__  ha_replica_label: __ha_replica__  # Максимальный период в прошлое на который мы можем делать  # PromQL запросы (1 год).  # Всё что больше будет обрезано до этого периода.  # Это нужно для реализации retention period.  # Для фактического удаления старых блоков нужно еще настроить lifecycle  # правило в бакете S3 на пару дней глубже  max_query_lookback: 8760h

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

Другой трудностью изначально было то, что Cortex не поддерживал гибкий список модулей, которые нужно активировать. Была возможность либо указать all, который на самом деле ни разу не all:

# cortex -modulesalertmanagerallcompactorconfigsdistributor *flusheringester *purger *querier *query-frontend *query-schedulerruler *store-gateway *table-manager *Modules marked with * are included in target All.

Либо указать строго один модуль.

Поэтому пришлось сделать пулл-реквест чтобы добавить возможность загружать список любых модулей. В данном случае мы используем all + compactor, ruler и alertmanager

Хранилище

storage:  # Выбираем хранилище Blocks Storage  engine: blocks# Конфигурируем егоblocks_storage:  # Тип бэкенда  backend: s3    # Параметры доступа к S3  s3:    endpoint: 127.0.0.1:9001    bucket_name: cortex    access_key_id: foo    secret_access_key: bar    # TLS у нас нет    insecure: true    tsdb:    # Где хранить локальные блоки до загрузки в S3    dir: /data/cortex/tsdb    # Через какое время их удалять    retention_period: 12h    # Сжимать Write-Ahead Log    wal_compression_enabled: true  bucket_store:    # Где хранить индексы блоков, найденных в S3    # По сути это должно быть в модуле Store-Gateway,    # но по какой-то причине тут    sync_dir: /data/cortex/tsdb-sync    # Как часто сканировать S3 в поиске новых блоков    sync_interval: 1m    # Настраиваем различные кеши на наши memcached    # Каждый кеш имеет свой префикс ключей, так что пересекаться они не будут    index_cache:      backend: memcached      memcached:        addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211    chunks_cache:      backend: memcached      memcached:        addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211    metadata_cache:      backend: memcached      memcached:        addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211

Distributor

distributor:  ha_tracker:    # Включить HA-трекер для Prometheus    enable_ha_tracker: true    # Таймаут после которого срабатывает failover на другую реплику Prometheus.    # Нужно настроить так чтобы метрики приходили не реже этого интервала,    # иначе будут ложные срабатывания.    ha_tracker_failover_timeout: 30s    # Настраиваем etcd для HA-трекера    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379  # Настраиваем etcd для Hash-Ring дистрибьютеров  ring:    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379

Ingester

ingester:  lifecycler:    address: 10.0.0.1    # Название зоны доступности    availability_zone: dc1    # Немного ждём чтобы всё устаканилось перед перераспределением    # токенов на себя    join_after: 10s    # Храним токены чтобы не генерировать их каждый раз при запуске    tokens_file_path: /data/cortex/ingester_tokens    ring:      # На сколько Ingester-ов реплицировать метрики.      # Если указана зона доступности, то реплики будут выбираться из разных зон      replication_factor: 2      # etcd для Hash-Ring Ingester-ов      kvstore:        store: etcd        etcd:          endpoints:           - http://ctx1:2379           - http://ctx2:2379           - http://ctx3:2379           - http://ctx4:2379

Querier

По поводу подбора правильных величин лучше почитать документацию.

Основная идея в том, чтобы никогда не запрашивать те блоки, которые еще не обработал Compactor:

querier:  # Временные файлы  active_query_tracker_dir: /data/cortex/query-tracker  # Запросы с глубиной больше этой будут направляться в S3  query_store_after: 6h  # Запросы с глубиной меньше этой отправляются в Ingester-ы  query_ingesters_within: 6h5mfrontend_worker:  frontend_address: 127.0.0.1:9095query_range:  # Запросы будут разбиваться на куски такой длины и выполняться параллельно  split_queries_by_interval: 24h  # Выравнивать интервал запроса по его шагу  align_queries_with_step: true  # Включить кеширование результатов  cache_results: true    # Кешируем в memcached  results_cache:    # Сжимаем    compression: snappy    cache:      # TTL кеша      default_validity: 60s      memcached:        expiration: 60s      memcached_client:        addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211

Store-Gateway

Этот модуль подгружает из S3 бакета заголовки блоков (комбинации меток, временные интервалы и т.п.).

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

store_gateway:  # Включаем шардинг  sharding_enabled: true  sharding_ring:    # Включаем zone awareness    zone_awareness_enabled: true    # Идентификатор зоны    instance_availability_zone: dc1    # Сколько реплик держать    replication_factor: 2    # Hash-ring для Store-Gateway    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379

Compactor

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

compactor:  # Временная директория для блоков.  # Должно быть достаточно много места чтобы можно было загрузить блоки,  # скомпактить их и сохранить результат.  data_dir: /data/cortex/compactor  # Как часто запускать компакцию  compaction_interval: 30m  # Hash-Ring для компакторов  sharding_enabled: true  sharding_ring:    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379

Ruler + AlertManager

Эти модули опциональны и нужны только если хочется генерировать алерты на основе правил.

  • Правила в стандартном Prometheus формате мы будем складывать в /data/cortex/rules/<tenant>/rulesN.yml на каждом хосте. Можно использовать для этого S3 или другие хранилища - см. документацию

  • Cortex периодически сканирует хранилище и перезагружает правила

  • Конфиги AlertManager в стандартном формате складываем в /data/cortex/alert-rules/<tenant>.yml

    Аналогично можно складывать в S3 и т.п.

  • Cortex запускает инстанс AlertManager (внутри своего процесса) отдельно для каждого tenant, если находит конфигурацию в хранилище

ruler:  # Временные файлы  rule_path: /data/cortex/rules-tmp  # Включаем шардинг  enable_sharding: true  # Какому AlertManager-у сообщать об алертах  alertmanager_url: http://ctx1:9009/alertmanager  # Откуда загружать правила  storage:    type: local    local:      directory: /data/cortex/rules    # Hash-ring для Ruler-ов  ring:    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379alertmanager:  # Где хранить состояние алертов  data_dir: /data/cortex/alert-data  # Внешний URL нашего инстанса (нужен для генерации ссылок и т.п.)  external_url: http://ctx1:9009/alertmanager  # Кластеринг - какой адрес слушать и какой анонсировать пирам  cluster_bind_address: 0.0.0.0:9094  cluster_advertise_address: 10.0.0.1:9094  # Список пиров  peers:    - ctx2:9094    - ctx3:9094    - ctx4:9094  # Откуда загружать настройки  storage:    type: local    local:      path: /data/cortex/alert-rules

Заключение

Вот и всё, можно запускать все сервисы - сначала зависимости, потом Cortex, затем - Prometheus.

Я не претендую на полноту, но этого должно быть достаточно чтобы начать работать.

Нужно учитывать, что Cortex активно развивается и на момент написания статьи часть параметров в master-ветке и документации (которая генерируется из неё) уже объявлено deprecated. Так что, вполне возможно, в новых версиях нужно будет конфиги немного исправлять.

Если есть вопросы и\или замечания - пишите, постараюсь добавить в статью.

Подробнее..

Оценка достоверности отчетов об ошибках с помощью анализа временных рядов

01.02.2021 16:08:09 | Автор: admin
Source: W. Playfair.Source: W. Playfair.

Привет, Хабр! Меня зовут Илья Селицер. В DINS мы участвуем в разработке продукта для UCaaS-провайдера RingCentral, который объединяет много функций от звонков и факса до корпоративного мессенджера и видеоконференций. Я, среди прочего, отвечаю за качество этого сервиса. В повседневной практике мне постоянно приходится анализировать взаимодействие различных сетевых элементов, которые участвуют в предоставлении той или иной услуги абонентам.


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


Статья будет интересна telco-инженерам и прочим специалистам, которым необходимо проанализировать данные мониторинга, сигнальную нагрузку, качество аудио- и видеовызовов, и другие процессы, представленные в виде временных рядов.


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

Для оценки качества услуги UCaaS-провайдеры могут использовать отчеты об ошибках, формируемые на основе сообщений об ошибках от различных сетевых элементов. Эти отчеты представляются в удобном для анализа виде: таблицы, графики и т.д. Но далеко не всегда по виду таблицы или графика можно сказать, насколько достоверно они отражают процессы, происходящие в сложной многокомпонентной системе. В этом случае для анализа можно использовать различные статистические методы, в частности, анализ временных рядов и тестирование гипотез.


Рассмотрим пример: UCaaS-провайдер предоставляет некоторую услугу S (например, прием факсовых, мультимедийных и прочих сообщений, голосовую почту, запись аудио- или видеозвонка) бизнес-абонентам в нескольких регионах Ri. Процесс предоставления S имеет следующие особенности:

  • Результат успешного предоставления услуги (SS) файл Fk некоторого объёма, который сохраняется в течение определённого периода времени в сетевом хранилище. Объём Fk пропорционален размеру сообщения.

  • Информация о количестве фактов успешного предоставления S для каждого Ri достоверна.

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

  • Отчёт об ошибках по региону SERi формируется для каждого Ri путем суммирования ERj по отчетам от каждого NEij.

Нам нужно оценить, насколько достоверно отчёты SERi отражают реальное количество ошибок, а также спланировать дальнейшие действия в зависимости от результатов оценки. До начала анализа определимся с действиями:

  • Результаты оценки достоверно. Не делаем ничего, т.е. не тратим ресурсы на дальнейший анализ. Оцениваем качество услуги на основе SERi и количества жалоб на качество услуги со стороны абонентов.

  • Результаты оценки недостоверно. Изменяем механизм формирования ERj. Это весьма ресурсоемкая задача, так как потребуется не только дополнительный анализ сценариев взаимодействия, но, возможно, доработка сетевых элементов и даже изменение архитектуры предоставления S.

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

  1. Потребление S растёт со временем. Это обусловлено, например, ростом абонентской базы и/или растущим спросом на S.

  2. Количество запросов на предоставление S изменяется с некоторой периодичностью, характерной для бизнес-пользователей, например, рабочие дни-выходные.

  3. Количество SE растет с ростом потребления, т.е. количества фактов успешного оказания услуги, например, из-за перегрузки сетевых элементов.

  4. Количество SE растет с ростом среднего объёма Fk. Например, количество ошибок при приёме факсового сообщения вырастет с ростом размера принимаемых факсовых сообщений (и, соответственно, объёма файлов Fk): приём 10-страничного факса потребует больше времени, чем 1-страничного, вероятность возникновения ошибки при приёме увеличивается с ростом времени приёма при прочих равных условиях. Следовательно, число ошибок при приёме большого количества 10-страничных факсов будет больше, чем при приёме такого же количества 1-страничных.

Исходя из утверждений 1-4, а также учитывая, что зависимости SS и SE от времени суть временные ряды (time series, TS), используем следующие возможные результаты анализа TS SE (TSE), по которым будем определять степень достоверности информации, предоставляемой отчётами об ошибках:

  • TSE стационарен, т.е. не имеет сезонности и тренда.

  • TSE нестационарен, т.е. демонстрирует сезонность (seasonality) и тренд (trend) это и будет принято в качестве критерия достоверности данных, предоставляемый TSE.

Нестационарный TS может и не иметь тренда, один из примеров случайное блуждание (random walk, RW), хотя этот факт применительно к RW, в некоторых случаях, подлежит обсуждению.
В качестве инструментов анализа будем использовать различные методы тестирования стационарности TS, а также оценку автокорреляции TS (ACF). Используем TSS и TSE для одного из регионов, полученные на реальной сети UCaaS-провайдера. Для анализа используем Python и соответствующие статистические и графические библиотеки. Код доступен здесь, для просмотра кода и графиков можно использовать nbviewer.

TSS

Сначала проанализируем TSS. Построим графики TSS и декомпозиции TSS, коррелограмму рис. 1-3. Для декомпозиции использованы следующие значения параметров:

  • model = multiplicative. Мы рассматриваем TSS как произведение трёх компонент: тренда, сезонных колебаний и случайной величины (остаток, residuals) [1]. На мультипликативную модель указывает, например, возрастающий со временем размах сезонных колебаний.

  • period = 7 (period = None даст тот же результат).

Рис. 1. TSS Рис. 1. TSS Рис. 2. Декомпозиция TSSРис. 2. Декомпозиция TSSРис. 3. Коррелограмма TSSРис. 3. Коррелограмма TSS

Анализ графиков на рис. 1-3 приводит к следующим выводам:

  • Сезонность чётко различима и равна 7 дням (рис.1).

  • Присутствует положительный тренд (рис. 2). Это случай детерминированного тренда: несмотря на скачкообразные изменения значений тренда, его наклон (slope coefficient) возвращается к исходному значению на более длительном отрезке времени.

  • Величина автокорреляции уменьшается с ростом лага. Это обусловлено трендом, тогда как форма, напоминающая затухающую синусоиду, это следствие сезонности. В нашем случае корреляция между значениями TSS в момент t максимальна в момент t n*7 дней, n = 1 5. Закрашенная область на рис. 3 это доверительный интервал (confidence interval, CI) для ACF. Значения ACF, попадающие за пределы этой области, статистически значимы.

Наличие сезонности и тренда говорит о нестационарности TSS [1]. Проверим TSS на стационарность с помощью ADF-теста (Augmented Dickey-Fuller). ADF-тест в качестве нулевой гипотезы (H0) постулирует наличие единичного корня [2, 3], что указывает на нестационарность TS. При этом отсутствие единичного корня не говорит о стационарности TS. Получаем следующие результаты:

p-value > 0.05, результат не является статистически значимым, H0 не может быть отвергнута.
Как и предполагалось до начала анализа, TSS нестационарен, что подтверждается собственно графиком процесса, результатами декомпозиции, коррелограммой, ADF-тестом.

Объём Fk

Как указано выше, размер сообщений пропорционален объёму Fk. Оценим, как меняются значения Fk со временем. Гистограмма с линейным масштабом Fk см. рис. 4, с логарифмическим масштабом см. рис. 5, коробчатая диаграмма (box plot) см. рис. 6.

Рис. 4. Гистограмма Fk, линейный масштабРис. 4. Гистограмма Fk, линейный масштабРис. 5. Гистограмма Fk, логарифмический масштабРис. 5. Гистограмма Fk, логарифмический масштаб

Некоторые выводы по рис. 4, 5:

  • Распределение несимметрично, присутствует правый хвост (right-skewed).

  • Среди значений объёма Fk присутствуют экстремальные значения/выбросы (outliers).

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

Рис. 6. Коробчатая диаграмма FkРис. 6. Коробчатая диаграмма Fk

Коробчатая диаграмма на риc. 6 показывает, что:

  • Количество экстремальных значений Fk > 55 заметно уменьшилось;

  • Медиана несколько выросла, несмотря на колебания от месяца к месяцу;

  • Характер распределения Fk примерно одинаков от месяца к месяцу. Средние, медианные и максимальные значения Fk по месяцам:

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


Информация, представленная в виде графиков на рис. 1-6, в значительной степени совпадает с предположениями 1-4.

TSE

Построим TSE и попытаемся обнаружить и оценить особенности этого графика.

Рис. 7. TSEРис. 7. TSE

График на рис. 7 не показывает явно выраженную сезонность, также присутствуют нулевые значения SE.


Проведём декомпозицию TSE см. рис. 8. Параметр model=additive т.к. в TSE присутствуют нулевые значения, period = None.

Рис. 8. Декомпозиция TSEРис. 8. Декомпозиция TSE

Декомпозиция TSE указывает на сезонность с периодом 7 дней, а также на отсутствие выраженного тренда.

Рис. 9. Коррелограмма TSEРис. 9. Коррелограмма TSE

Коррелограмма на рис. 9 также не показывает сезонность, как в случае TSS: всего два значения статистически значимы, остальные значения статистически незначимы. В то время как декомпозиция TSE указывает на наличие сезонности TSE с периодом 7 дней, коррелограмма TSE не показывает заметной автокорреляции с лагом, кратным 7 (рис. 9). Тот факт, что используемый для данного случая алгоритм декомпозиции обнаружил сезонную компоненту, не означает, что сезонность присутствует или существенно определяет свойства TSE: даже для RW этот алгоритм декомпозиции обнаруживает сезонную компоненту. В этом случае необходимо использовать другие доступные методы оценки стационарности TSE.


ADF-тест даёт следующие результаты:

Значение p-value << 0.05, результат статистически значим, принимаем альтернативную гипотезу (H1): TSE стационарен. Для более надежной проверки, в дополнение к ADF-тесту используем KPSS-тест (Kwiatkowski-Phillips-Schmidt-Shin). В отличие от ADF-теста, KPSS-тест постулирует в качестве H0 стационарность процесса, а в качестве H1 наличие единичного корня. ADF- и KPSS-тестам свойственны некоторые недостатки, например, высокий уровень ошибок первого рода (ошибочное отвержение H0), однако совместное применение ADF- и KPSS-тестов позволит уменьшить влияние этих недостатков на результаты анализа TSE [3].
KPSS-тест даёт следующие результаты:

Здесь p-value > 0.05, т.е. не является статистически значимым, принимаем H0: TSE стационарен.


Приходим к следующим выводам:

  1. На основании анализа коррелограммы TSE, а также ADF- и KPSS-тестов, считаем, что TSE стационарен.

  2. Стационарность TSE говорит о том, что данные в отчетах об ошибках не соответствуют нашим представлениям о системе, т.е. наблюдаемому поведению системы.

  3. Механизм формирования отчётов об ошибках нуждается в более детальном исследовании и последующей корректировке.

Заключение

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


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

Литература

  1. Athanasopoulos, G., Hyndman, R. J. Forecasting: principles and practice, 2nd edition.

  2. Brockwell, P. J., Davis, R. A. (2016). Introduction to Time Series and Forecasting, 3rd edition. Springer Texts in Statistics

  3. Pal, A., Prakash, PKS (2017). Practical Time Series Analysis, Packt Publishing Ltd.

Подробнее..

Recovery mode Vela умный кеш для time series и не только

11.07.2020 10:23:53 | Автор: admin

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


Фламинго


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

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


Допустим, мы собираем курсы для трех пар валют. Простейшее определение Vela для хранения актуального состояния будет выглядеть как-то так:


defmodule Pairs do  use Vela,    eurusd: [sorter: &Kernel.<=/2],    eurgbp: [limit: 3, errors: 1],    eurcad: [validator: Pairs]  @behaviour Vela.Validator  @impl Vela.Validator  def valid?(:eurcad, rate), do: rate > 0end

Обновление значений


Vela.put/3 функция последовательно сделает следующее:


  • вызовет validator на значении, если таковой определен (см. главку Валидация ниже);
  • добавит значение либо в ряд хороших значений, если валидация закончилась успешно, или в служебный ряд :__errors__ в обратном случае;
  • вызовет сортировку если sorter определен для данного ключа, или просто положит значение в голову списка (FILO, см. главку Сортировка ниже);
  • обрежет ряд в соответствии с параметром :limit переданном при создании;
  • вернет обновленную структуру Vela.

iex|1 > pairs = %Pairs{}iex|2 > Vela.put(pairs, :eurcad, 1.0)# %Pairs{..., eurcad: [1.0], ...}iex|3 > Vela.put(pairs, :eurcad, -1.0)#%Pairs{__errors__: [eurcad: -1.0], ...}iex|4 > pairs |> Vela.put(:eurusd, 2.0) |> Vela.put(:eurusd, 1.0)#%Pairs{... eurusd: [1.0, 2.0]}

Также Vela имплементирует Access, так что можно для обновления значений воспользоваться любой из стандартных функций для глубокого обновления структур из арсенала Kernel: Kernel.get_in/2, Kernel.put_in/3, Kernel.update_in/3, Kernel.pop_in/2, and Kernel.get_and_update_in/3.


Валидация


Валидатор можен быть определен как:


  • внешняя функция с одним аргументом (&MyMod.my_fun/1), она получит только значение для валидации;
  • внешняя функция с двумя аргументами, &MyMod.my_fun/2, она получит пару serie, value для валидации;
  • модуль, имплементирующий Vela.Validator;
  • конфигурационный параметр threshold, и опционально compare_by, см. главку Comparison ниже.

Если валидация прошла успешно, значение добавляется в список под соответствующим ключом, в обратном случае кортеж {serie, value} отправляется в :__errors_.


Сравнение


Значения, сохраняемые в этих рядах, могут быть любыми. Чтобы научить Vela их сравнивать, необходимо передать compare_by параметр в определение ряда (если только значения не могут быть сравнены стандартным Kernel.</2); этот параметр должен иметь тип (Vela.value() -> number()). По умолчанию это просто & &1.


Также, в определение ряда можно передать параметр comparator для вычисления значений дельт (min/max); например, передавая Date.diff/2 в качестве компаратора, можно получить правильные дельты для дат.


Другим удобным способом работы является передача параметра threshold, который определяет максимально допустимое отношение нового значения к {min, max} интервалу. Поскольку он задан в процентах, проверка не использует comparator, но все еще использует compare_by. Например, чтобы указать пороговое значение для времени дат, необходимо указать compare_by: &DateTime.to_unix/1 (для получения целочисленного значения) и threshold: 1, в результате чего новые значения будут разрешены, только если они находятся в band интервале от текущих значений.


Наконец, можно использовать Vela.equal?/2 для сравнения двух кешей. Если значения определяют функцию equal?/2 или compare/2, то эти функции будут использованы для сравнения, в противном случае мы тупо используем ==/2.


Получение значений


Обработка текущего состояния обычно начинается с вызова Vela.purge/1, который убирает устаревшие значения (если validator завязан на timestamps). Затем можно вызвать Vela.slice/1, которая вернет keyword с именами рядов в качестве ключей и первым, актуальными значениями.


Также можно воспользоваться get_in/2/pop_in/2 для низкоуровнего доступа к значениям в каждом ряду.


Приложение


Vela может оказаться чрезвычайно полезной в качестве кеша временных рядов в стейте процесса типа GenServer/Agent. Мы хотим никогда не использовать устаревшие значения курсов, и для этого мы просто держим процесс с состоянием, обрабатываемым Vela, с валидатором, показанным ниже.


@impl Vela.Validatordef valid?(_key, %Rate{} = rate),  do: Rate.age(rate) < @death_age

и Vela.purge/1 спокойно удаляет все устаревшие значения каждый раз, когда нам требуются данные. Для доступа к актуальным значениям, мы просто вызываем Vela.slice/1, а когда требуется небольшая история по курсу (весь ряд целиком), мы просто возвращаем его, уже отсортированным, с провалидированными значениями.




Удачного кеширования временных рядов!

Подробнее..

Recovery mode O tempora, o mores!

05.09.2020 10:09:13 | Автор: admin

Для протокола: заголовок я позаимствовал у Цицерона, в Oratio in Catilinam Prima in Senatu Habita.


Cicero Denounces Catiline, fresco by Cesare Maccari, 18821888




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


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


Timelines of my busy hours and the dentists busy hours


Беглого взгляда на эту диаграмму достаточно, чтобы определить время назначенной встречи. Завтра, в обеденное время. Легко, да?


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


Итак, встречайте Tempus!


Детали реализации


Сущность, вокруг которой построена вся библиотека Slot. Он представляет временной интервал самым естественным и простым способом: это структура, с полями from и to, оба типа DateTime. Набор слотов хранится в структуре Slots, под капотом реализованной как AVLTree. Этот выбор был сделан для того, чтобы сохранить базовый список слотов согласованным (упорядоченным и не перекрывающимся) с наименьшими затратами при оптимизации доступа по чтению и записи. Обычно список слотов заполняется при инициализации и потом используется для проверки, поиска свободных интервалов, и тому подобного.


Функция Slots.add/2 добавит в список слотовновый, объединяя слоты по мере необходимости. Это позволяет просто вставлять новые слоты в конструкцию, не беспокоясь о порядке и перекрытии. Также предусмотрена вспомогательная функция Slots.merge/2 для объединения двух наборов слотов. Последнее особенно удобно, когда нужно, например, найти пустой слот в обеих сериях, как в примере с выбором времени посещения дантиста выше.


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


Модуль Tempus


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


Вот незамысловатый пример из тестов:


slots =  [    Tempus.Slot.wrap(~D|2020-08-07|), # whole day    %Tempus.Slot{      from: ~U|2020-08-08 01:01:00Z|, # one minute      to: ~U|2020-08-08 01:02:00Z|    },    %Tempus.Slot{      from: ~U|2020-08-08 01:03:00Z|, # one minute      to: ~U|2020-08-08 01:04:00Z|    }  ]  |> Enum.into(%Tempus.Slots{})

Если добавить 0 секунд ко времени, уже занятому слотом, вернется первое доступное время после занятого промежутка.


Tempus.add(slots, ~U|2020-08-08 01:01:30Z|, 0, :second)# ~U[2020-08-08 01:02:00Z]

Добавив 70 секунд ко времени, на пять секунд предшествующему первому занятому слоту~U[2020-08-08 01:00:55Z]вернется экземпляр DateTime через пять секунд после второго занятого слота (5sec + занято + 60sec + занято + 5sec):


Tempus.add(slots, ~U|2020-08-08 01:00:55Z|, 70, :second)# ~U[2020-08-08 01:04:05Z]

И так далее. Разумеется, можно добавлять и отрицательные значения.


Слияние наборов слотов


Slots.merge/2 понимает Stream в качестве второго аргумента. На данный момент библиотека не поддерживает слияние и использование двух потоков слотов, но вливание потока в существующие временные интервалы возможно. Это может быть полезно, когда у нас есть короткий список, скажем, праздников, и мы хотим объединить его с повторяющимися слотами, например с выходными.


Все функции, возвращающие Slots и / или Slotгарантированно отдают допустимые объекты (нормализованные, упорядоченные и объединенные по мере необходимости).


Что еще?


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




Удачных временных интервалов!

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru