YAWNDB — time series база данных

YAWNDB

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

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

Немного теории

На любом графике сетевой активности отображаются изменения некоторых параметров в течение определенного периода времени (за месяц, за неделю, за сутки и т.п.) Чтобы построить график, необходимо обработать статистический материал, представляющий собой совокупность пар «время — значение» за указанный временной промежуток. Такой материал называется временным рядом (англ. time series).

Для анализа временных рядов существует довольно много программных инструментов. Их общей чертой является использование кольцевых баз данных (англ. Round Robin database). Кольцевой называется база данных, в которой объем хранимых данных не изменяется со временем, поскольку количество данных постоянно: ячейки базы данных задействуются циклически.

В кольцевой базе данных хранится один или несколько наборов данных, объединяемых в архивы (англ. Round Robin Archives, RRA). По своей структуре кольцевые таблицы аналогичны массивам, у которых адрес последнего элемента совпадает с адресом первого элемента. Положение последнего обновленного элемента хранится в виде указателя. Архивы связаны между собой так, что каждый последующий архив хранит информацию из предыдущего: один архив сохраняет данные с небольшим интервалом между записями, другой через заданное количество интервалов сохраняет консолидированные данные из предыдущего, третий делает это еще реже и так далее.

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

RRDTool

Самым известным и распространенным инструментом для анализа временных рядов и последующей визуализации является, конечно же, RRDTool.

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

Когда количество файлов, в которые мы записывали данные, превысило 1000, начали возникать проблемы: например, запись этих самых данных стала занимать слишком много времени. Иногда данные просто не записывались, хотя никаких ошибок или перебоев в работе не возникало.

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

Уменьшить число операций записи на диск при работе с большими объемами данных в RRD Tool можно при помощи демона RRDcacheD, который кэширует данные и после накопления определенного объема записывает в базу. Однако практика показала, что для решения наших задач RRDcacheD подходит плохо.

Аккумулируя данные, он в то же время не дает прочитать их из кэша, а только записывает на диск. Если нужно каким-то образом обработать данные, то потребуется все записать на диск и потом считывать с диска. Чем больше объем данных, тем хуже работает кэш: сильно загружается жесткий диск, создается дополнительная нагрузка на процессор…

Еще одна особенность RRDcacheD заключается в том, что он пишет данные на диск в самый неожиданный и неподходящий момент.

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

Столкнувшись со всеми описанными трудностями, мы решили от использования RRDTool отказаться. Пробовали мы и другие средства обработки и визуализации данных — например, graphite, не устроивший нас по причине низкой производительности.

В ходе знакомства с существующими инструментами мы все отчетливее понимали, что ни один из них под нашу специфику не подходит. У нас возникло желание разработать собственное решение, полностью отвечающее всем нашим требованиям. Основными требованиями к продукту были, во-первых, гибкость, настраиваемость и возможность адаптации под специфику наших сервисов, а во-вторых — высокая производительность. Так появилась кольцевая in-memory база данных YAWNDB.

YAWNDB: общая информация

YAWNDB (это название с английским глаголом to yawn — «зевать» — ничего общего не имеет; акроним YAWN означает Yet Another iNvented Wheel) представляет собой in-memory базу данных; все данные хранятся в памяти и периодически записываются на диск. Она написана на Erlang. Модель легковесных процессов, лежащая в основе этого языка, позволяет обеспечить быструю обработку больших наборов данных при небольшом потреблении системных ресурсов.

Поступающие в YAWNDB данные распределяются по архивам (в терминологии YAWNDB называемым также корзинами — buckets) в соответствии с некоторыми правилами.

Под правилом понимается набор свойств для той или иной статистики (размер данных, период сбора и т.п.).

Все эти данные представляют собой триплеты, состоящие из времени, значения и ключа (ключ в терминологии YAWNDB называется также путем). Путь представляет собой последовательность строчных букв и цифр, определяющую, где именно должен быть сохранен триплет. В этой последовательности наиболее важен первый компонент — префикс. Правило включает в том числе и поле Prefix и таким образом определяет, как хранятся данные для конкретного префикса. Для одного и того же префикса можно создавать несколько правил. В таком случае триплет будет записан в базу в соответствии с каждым из этих правил.

Это означает, что конкретная пара «время — значение» поступит в N корзин, где N — количество правил, соответствующих префиксу пути для данной пары.

Используемый нами подход позволяет получить следующие преимущества:

  • удаление утративших актуальность данных без излишних затрат ресурсов;
  • фиксированное потребление памяти для определенного количества ключей;
  • фиксированное время доступа к произвольной записи.

Внутреннее устройство

В основе YAWNDB лежит round-robin алгоритм, реализованный в библиотеке Ecirca на языке С.

Другие модули, написанные на Erlang взаимодействуют с ней при помощи NIF (Native Implemented Functions).Для сохранения данных на диске используется Erlang-приложение Bitcask.

REST API построен на базе веб-сервера Cowboy.
Запись данных осуществляется через сокет, а считывание — с помощью REST API-интерфейса.

Установка

Для работы с YAWNDB нужно обязательно установить парсер LibYAML. Затем нужно клонировать репозиторий:

$ git clone git@github.com:selectel/yawndb.git

И выполнить следующие команды:

$ cd yawndb
$ make all

Запуск YAWNDB осуществляется при помощи команды:

$ ./start.sh

Перед запуском необходимо скопировать пример конфигурационного файла на место настоящего:

$ cp priv/yawndb.yml.example priv/yawndb.yml

Или же создать соответствующий симлинк.

Конфигурирование

Все настройки YAWNDB хранятся в конфигурационном файле yawndb.yml.

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

rules:
    # User statistics
    # детализированная статистика по минутам (за 24 часа)
    - name: per_min
      prefix: clientstats
      type: sum
      timeframe: 60
      limit: 1440
      split, backward
      value_size, large
      additional_values: []

    # статистика по часам (за последний месяц)
    - name: per_hour
      prefix: clientstats
      type: sum
      timeframe: 3600
      limit: 720
      split: backward
      value_size: large
      additional_values: []

    # статистика по дням (хранить за последние два года)
    - name: per_day
      prefix: clientstats
      type: sum
      timeframe: 86400
      limit: 730
      split: backward
      value_size: large
      additional_values: []

Примеры использования

Формирование пакета для записи данных (Python)

def encode_yawndb_packet(is_special, path, time, value):
    """
Сформировать пакет данных для отправки в yawndb.    

    :param bool is_special: специальное значение? используется для additional_values
    :param str path: идентификатор метрики
    :param int value: значение метрики
    """
    is_special_int = 1 if is_special else 0
    pck_tail = struct.pack(
">BBQQ",
YAWNDB_PROTOCOL_VERSION, is_special_int, time, value
) + path
    pck_head = struct.pack(">H", len(pck_tail))
    return pck_head + pck_tail

Формирование пакета для записи данных на С

// задаем версию протокола
#define YAWNDB_PROTOCOL_VERSION 3
// описываем структуру пакета 
struct yawndb_packet_struct {
    uint16_t    length;
    uint8_t     version;
    int8_t      isSpecial;
    uint64_t    timestamp;
    uint64_t    value;
    char        path[];
};
// формируем пакет
yawndb_packet_struct *encode_yawndb_packet(int8_t isSpecial,
        uint64_t timestamp, uint64_t value, const char * path) {
    yawndb_packet_struct *packet;
    uint16_t length;

    lenght = sizeof(uint8_t) + sizeof(int8_t) + sizeof(uint64_t) + sizeof(uint64_t) + strlen(path);
    packet = malloc(length + sizeof(uint16_t));
    packet->length = htobe16(length);
    packet->version = YAWNDB_PROTOCOL_VERSION;
    packet->isSpecial = isSpecial;
    packet->timestamp = htobe64(timestamp);
    packet->value = htobe64(value);
    strncpy(packet->path, path, strlen(path));

    return packet;
}

Заключение

Сегодня YAWNDB используется нами как в публичных сервисах, так и на внутренних проектах. Исходный код проекта размещен на GitHub. Мы будем рады, если кто-то из наших читателей воспользуется нашим продуктом. Будем также очень благодарны за замечания и предложения по его улучшению.

28 марта мы опубликовали сайт посвященный нашим Open Source разработкам: selectel.io. На нем можно найти подробную документацию к YAWNDB на русском языке, а также информацию о других наших проектах.