VRackDB - это простая In Memory Graphite like база данных, предназначенная для хранения временных рядов (графиков). (TypeScript)

Особенности:

  • Очень простая/экономичная/быстрая

  • Хранит данные в памяти. При закрытии программы данные будут потеряны

  • Резервирует память метрики, последующее добавление данных метрики не увеличивает потребление памяти

  • Всегда возвращает данные в виде набора данных для графика

  • Имеет простой формат запросов

  • Агрегация и модификаторы данных

  • Оперирует временем в целых секундах

Как она может помочь?

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

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

Если складывать данные в массив каждые 5 секунд, то на графике будет 300 точек, соответственно, будет помещаться всего 25 минут. А если загрузка будет идти 2 дня? Что же тогда делать?

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

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

Давайте глянем как это сделать:

import { Database } from "vrack-db";
// или
const { Database } = require('vrack-db')
// Создаем инстанс базы данных
const DB = new Database()

DB.scheme('test', 'test.metric.*', '5s:1h, 15s:3h, 1m:24h, 5m:1w, 15m:1mon, 1h:1y')

const now = Math.floor(Date.now() / 1000)

// Делаем вид, что мы пишем что-то полезное в базу 
for(let i = 1; i <= 300; i++){
  DB.write(
    'test.metric.id', // Идентификатор метрики
    Math.sin(i/100), // Значение
    (i * 5) + now  // Время
  )
}

/* Вернет что-то типа 
{
    start: 1714994924,
    end: 1714995220,
    relevant: true,
    rows: [ {time: 1714994924, value. 0.2312305}}, {…}, {…}, … ]
​} */
const result = DB.readAll('test.metric.id', 300)

В итоге мы получим вот такой график:

Если мы накинем данных до 6 часов:

Теперь сутки:

Ну и трое суток:

Причем данные мы можем набирать до года и каждый раз мы будем получать красивый график, состоящий из 300 точек, который будет линеен во времени. Конечно, использовать синус как набор данных - не очень хорошая практика, ведь у нас нет тренда и мы просто видим как сжимается синус. В реальном графике тренд будет прослеживаться куда лучше.

Давайте посмотрим на особенности базы для решения этой задачи:

  • Память для метрики резервируется один раз и последующее добавление данных этой метрики не приводит к увеличению потребления памяти

  • Данные приводятся всегда к определенной точности, что значительно уменьшает проблему нелинейности

  • Для получения данных за определенный период нет необходимости перебирать все данные. База знает где лежат данные по времени

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

Вот примеры применения VRackDB:

  • Построение графика скачивания/копирования/выполнения работы

  • Анализ потребления памяти приложения/CPU/GPU/других метрик производительности

  • Диагностика задержек HTTP/API/WebScoket и других запросов

  • Количественный анализ успешных/не успешных операций в интервал времени

  • Приложения для SOC компьютеров и устройств на их основе (мультиметры и т.п.)

  • Кеширование данных для быстрого отображения/анализа графиков/упрощения данных

Мой публичный проект, который использует VRackDB - VGranite - софт для преобразователя интерфейсов serial<->ethernet. VGranite строит графики траффика общения между элементами сети внутри себя. Хоть это и мелочь, но очень приятная, и позволяет быстро оценить динамику стабильности работы.

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

База данных хранит все данные в памяти, что не мучает ваш SSD запросами мелких операций записи. Сейчас стараюсь внедрять эту базу там, где это уместно. Не всегда хочется тратить ресурс хранилища на оценочные метрики. А вот ресурс оперативной памяти в целом не жалко. Даже если я потрачу ~100МБ оперативы на 1000 метрик - для сервера это копейки.

VRackDB, можно сказать, не имеет зависимости. Единственная ее зависимость это Buffer, для запуска в браузере с использованием, например, vuejs. Хотя сейчас я сомневаюсь, что это хорошее решение, возможно, в будущем откажусь от зависимости. Идея была использовать ее с electron. Но в electron можно держать базу и на стороне бэка. Поэтому, как пойдет дальше - видно будет, может и откажусь.

Ну что? Если вас не заинтересовало - можно дальше и не читать.

Схемы

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

Дело в том, что в VRackDB метрики хранятся на слоях, которые имеют разную точность (интервал) и общий период. Таким образом достигается высокая точность актуальных данных и сохраняется тренд более старых данных.

Пример добавление схемы:

DB.scheme('test', 'test.name.*', '5s:10m, 1m:2h')
  • test - Название схемы - это именованная коллекция метрик

  • test.name.* - Все метрики с маской test.name.* будут попадать в схему test.

  • 5s:10m, 1m:2h - Указания точности и размера периода хранения метрик.

Что это за 5s:10m, 1m:2h? Каждое значение через запятую добавляет в схему слой с определенной точностью (интервалом) и общим периодом.

Так что же такое слой? Слой - это хранилище данных, которое распределяет данные внутри себя по ячейкам памяти в зависимости от его длины и интервала. Вот как можно представить визуально слои 15m:6h, 1h:1d (15 мин:6 часов, 1 час: 1день):

Время записи всегда приводится к точности слоя (интервалу слоя). В слой 15m:6h нельзя записать данные чаще 15 минут. При попытке записи чаще, чем интервал - данные будут перезаписаны, используя модификатор (по умолчанию last - последнее значение заменяет прошлое).

Слой 15m:6h не может хранить данные дольше 6 часов. Новые данные будут смещать старые, удаляя их из слоя.

Хранение метрики выглядит как пирог из слоев, ячейки которых постоянно смещаются влево, теряя данные по мере заполнения ячеек справа.

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

Значение типа 5s, 10m, 2h, являются интервалами, которые можно расшифровывать как 5 секунд, 10 минут, 2 часа.

Вот список поддерживаемых интервалов:

  • s - секунды

  • m - минуты

  • h - часы

  • d - дни

  • w - недели

  • mon - месяцы

  • y - годы

Интервалы начинаются с целого числа, после которого сразу идет тип интервала, например 1m, 10d, 120s, 1y.

Вот некоторые рекомендации при указании точности схемы:

  • Cлои располагаются друг над другом - если ваш самый длинный слой вмещает в себя год, все, что дольше года, вы будете терять.

  • Не стоит делать точные слои слишком длинными - в этом нет смысла. Точные и длинные слои можно делать только в некоторых редких случаях.

  • Нет смысла делать более точный слой длиннее менее точного.

  • Не стоит делать слишком много слоев (норма 6-8 слоев).

Если название метрики не подходит ни под один паттерн, будет использована схема по умолчанию. Она рассчитана примерно на 120 точек на слой, что достаточно мало. Рекомендуется создавать схемы самому под свои задачи.

Что дальше?

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

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

Сама база данных поставляется вместе с исходными кодами. Иногда бывает полезно глянуть именно исходник на TypeScript. Исходный код написан максимально просто и понятно, с вменяемым оформлением. Распространяется по лицензии Apache 2.0.

Развитие базы идет, она становится все более взрослой. С версии 2.0 был добавлен компонент ErrorManager. Теперь в базе все ошибки стали полностью формализованными. Вызовы ошибок можно найти в исходнике по коду ошибки. Обычно перед каждым классом идет определение, какие ошибки этот класс может вызывать.

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

Может кому пригодится. Намучившись с разными библиотеками отображения графиков, нашел для себя Apache ECharts. Для vue3 можно использовать vue-echarts. Мы на его основе рисуем красивые графики по типу таких:

Они в онлайн режиме могут полностью обновляться, вместе с настройками отображения, зонами, маркерами и т.п. Можно настроить так, чтобы были очень похожи на Grafana. Кстати, на графике выше, данные получены из VRackDB.

Телеграм канала не будет, уж простите - на этом все.

Комментарии (2)


  1. adeshere
    08.05.2024 22:12

    VRackDB - это простая In Memory Graphite like база данных, предназначенная для хранения временных рядов (графиков). (TypeScript)

    Просто интересно, а сколько человеко-дней занимает такая разработка сейчас (в наше время)?

    Мы лет 30-40 назад написали примерно то же самое на фортране, только с акцентом на анализ/визуализацию ретроспективно накопленных данных. Система до сих пор используется, и после нескольких рефакторингов насчитывает несколько сотен тысяч строк кода. Она тоже достаточно быстрая (пока ряды содержат меньше миллиона измерений, все делается "на лету"), с минимальными требованиями к "железу", но есть и некоторые отличия:

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

    • ОП у нас не используется вообще ;-) Ну то есть на самом деле там хранятся текущие графики (смешное количество точек), а при обработке рядов - скользящее окно, которое бежит по ряду (тут уже требования намного серьезнее). Но вот практически вся остальная доступная память напрямую программой не задействуется, а используется для кэширования дисковых файлов с данными, причем этим занимается ОС. Даже под DOS-ом это позволяло нам спокойно работать с миллионами точек. А сейчас у нас не редкость ряды из миллиардов значений. При этом они спокойно крутятся но компе с 8Гб памяти, где параллельно висит еще тьма всякой рутины от браузера до VS;

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

    набор "диапазонов" (т.е. пар {min,max}) той же размерности

    Поясню, зачем нужен диапазон. Пусть, например, у нас есть ряд с опросом в 1 сек, а длина ряда 5 лет. Тогда каждый пиксел экрана (пойнт графика) должен показать пользователю фрагмент сигнала, состоящий из примерно 100 000 первичных (секундных) отсчетов данных. Понятно, что за 100 тыс. отсчетов ряд мог довольно сильно гулять вверх-вниз. В такой ситуации показывать юзеру одно лишь только среднее значение... ну, такое себе. У нас в графиках поэтому сделаны два режима (чуть подобнее здесь): можно показывать либо среднее, либо размах колебаний (он рисуется вертикальной палочкой). Вот сравнение двух графиков одного и того же ряда с генерализацией среднего либо размаха:

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

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

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

    туда заглянуть

    вроде, статья должна осенью выйти. В elibary она за paywall-ом, но на сайте ИФЗ-шных журналов доступ к статьям свободный. Короче, если статья понравится, не забудьте сослаться. Вам не сложно, а нам будет приятно ;-)

    А вот "слои" у нас не прижились как-то. Хотя один из юзеров предлагал такой вариант. Но... небольшие ряды данных (до миллиона значений) на современных компьютерах агрегируются "на лету", поэтому на таких масштабах просто нет смысла заморачиваться с предвычисленными слоями. Когда рисовалка запрашивает у БД нужный ей фрагмент данных, то проще сделать все расчеты в динамике и вернуть данные в точности с нужной детальностью, а не подбирать наиболее подходящий слой (который вдобавок может оказаться не совсем подходящим). А для гигабайтных рядов мы обычно делаем всего один промежуточный слой, да и то вручную.

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

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

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


    1. ponikrf Автор
      08.05.2024 22:12
      +1

      Просто интересно, а сколько человеко-дней занимает такая разработка сейчас (в наше время)?

      На все, с документацией и структуризацией API не тратя на это полный рабочий день, написал ее за 2 месяца.

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

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

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

      Поскольку цели у нас разные - энергозатраты так сильно отличаются.

      Есть некоторые вещи которые я у вас подметил. Прочитав ваш комментарий - понимаю, что измерять в секундах это очень долго учитывая современные вычислительные мощности компьютеров. Подумаю, как внедрить поддержку вплоть до микросекунд. И идея с парами min/max мне тоже нравится. Думал на эту тему, но как реализовать не знал, в принципе теперь более ли менее понятно.

      Большое спасибо за такой развернутый комментарий!