Недавно мой знакомый Karl (имя изменено) проходил собеседование на должность DevOps и обратился ко мне с просьбой проверить его решение. Я почитал условие задачи и решил, что из нее бы вышел неплохой тест, поэтому немного расширил задачу и написал свою реализацию, а заодно попросил коллегу Alex подумать о своей реализации. Когда все три варианта были готовы, я сделал еще две сравнительные версии на C# и сел писать эту статью. Задача довольно проста, а соискатели находятся на неких ступенях эволюции из админов в программисты, которые я и хотел оценить.

Кому интересны грязные детали, необъективные тесты и субъективные оценки — прошу под кат.

Задача


По условию у нас есть текстовые логи с загрузкой CPU серверов и необходимо делать из них некие выборки.

Полный текст задания
Представьте себе систему мониторинга, в которой 1000+ серверов по несколько CPU записывают каждую минуту лог нагрузки в отдельный файл на выделенный сервер.

В итоге для 1000 серверов по 2 CPU за один день получается каталог с 1000 логами в текстовом виде по 2880 записей в таком формате:

1414689783 192.168.1.10 0 87
1414689783 192.168.1.11 1 93

Поля в файле означают следующее:
timestamp IP cpu_id usage

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

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

1. Команда QUERY — сводная статистика по серверу за диапазон времени
Синтаксис: IP cpu_id time_start time_end

*Время задается в виде YYYY-MM-DD HH:MM

Пример:

>QUERY 192.168.1.10 1 2014-10-31 00:00 2014-10-31 00:05
(2014-10-31 00:00, 90%), (2014-10-31 00:01, 89%), (2014-10-31 00:02, 87%), (2014-10-31 00:03, 94%), (2014-10-31 00:04, 88%)

2. Команда LOAD — средняя загрузка выбранного процессора для выбранного сервера
Синтаксис: IP cpu_id time_start time_end

Пример:

>LOAD 192.168.1.10 1 2014-10-31 00:00 2014-10-31 00:05
88%

3. Команда STAT — статистика всех процессоров для выбранного сервера
Синтаксис: IP time_start time_end

Пример:

>STAT 192.168.1.10 2014-10-31 00:00 2014-10-31 00:05
0: 23%
1: 88%

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

P.S. В изначальном задании подразумевалось, что это интерактивная программа, которая принимает команды с консоли после загрузки. Это не обязательно и программа может быть разбита на раздельные части для загрузки и для выполнения запросов. Т.е. допускается вариант с несколькими скриптами init.sh, query.sh, load.sh и т.д.

В целом, задача весьма прозрачна и в ней напрашивается использование БД, поэтому не удивительно, что все три варианта используют SQLite. Вспомогательные варианты на C# я уже сделал для сравнения скорости и они работают иначе.

Оценка


В готовых решениях я оценивал два фактора с соотношением 40/60%: скорость и качество кода. Методика оценки факторов приведена чуть ниже, но оба фактора никак не относятся к общему вопросу и не показывают степерь «админства» или «программерства», поэтому отдельно я кроме сухих баллов скорости/качества, вывел отдельно субъективную шкалу «админское решение», «программерское», «универсальное». Это ни в каком виде не соревнование и не сравнение скорости разных языков, а скорее оценка подходов к программированию.

Оценка скорости

По условию запрос должен выполняться меньше секунды, но не приведено ни оборудование, ни количество ядер, ни вообще архитектура тестовой станции. На мой взгляд это наводит на мысль, что тест должен выполняться на порядок или два быстрее, чтобы на адекватном оборудовании всегда оставаться в нужных рамках. Заодно это должно натолкнуть на идею масштабируемости — в задаче приводится пример на 2880000 записей за один день, но в реальных условиях их может быть заметно больше (больше серверов и ядер), а диапазон выборки может включать не дни, а месяцы и годы. Значит, идеальное решение должно не показывать зависимости от объема данных и не потреблять лимитированные ресурсы в неограниченном количестве. В этом случае бесконтрольное использование памяти (in-memory tables или хранение в массивах в памяти) это минус, а не плюс, потому как выборка за год на 10000 компьютеров по 8 процессоров это навскидку 42 048 000 000 записей, минимум по 10 байт каждая, т.е. ~420GiB данных. К сожалению, проверить такие объемы мне не удалось из-за ограничений доступной техники.

Для проверки скорости использовалась unix команда time (значение user), а для интерактивных решений — внутренние таймеры в программе.

Оценка качества

Под качеством я в основном понимаю универсальность — универсальность использования, поддержки, доработки. Нет особого смысла в коде, который работает только в строго заданных рамках и никак не может выйти за их пределы, обработать больше данных, быть изменен для других ситуаций и т.д. Например, код на x86 Assembler мог бы быть очень быстрым, но совершенно не гибким и простейшее изменение вроде перехода на IPV6 адреса могло бы стать для него очень болезненным. В первую очередь тут оценивалась обработка входящих параметров: нестандартные ситуации, выборки, дающие 0 результатов, не валидные запросы. Во-вторую — язык программирования, стиль кода, количество и качество комментариев.

Субъективная оценка

Сложно сказать, какой именно параметр определяет, насколько эволюционировал администратор до программиста. Лично я разделяю их по такому принципу: администратор работает с инструментами, а программист создает их. Разница примерно как между профессиональным гонщиком и автомехаником — механик зачастую неплохо водит и знает автомобиль досконально, но гонщик чувствует все заложенные в машину свойства и даже больше. Хороший админ знает скорость работы базы данных, понимает что такое горизонтальное и вертикальное масштабирование, каждый день пользуется индексами. Программист может написать свою БД, использовать вложенные деревья для них, может конвертировать все данные в собственный формат и оставить их на диске, хитрым образом расположенные для быстрого доступа.

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

Программы


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

Karl
Код на github
Язык: Python 2.7
Зависимости: нет
Интерактивная: да
БД: SQLite, in-memory table

Alex
Код на github
Язык: Python 2.7
Зависимости: progress, readline
Интерактивная: да
БД: SQLite, in-memory table

Nomad1
Код на github
Язык: Bash
Зависимости: нет
Интерактивная: нет
БД: SQLite
Особенность: внешний .db файл для работы

Nomad2
Код на github
Язык: C#
Зависимости: mono
Интерактивная: да
БД: нет
Особенность: Хэш-таблица по IP адресам

Nomad3
Код на github
Язык: C#
Зависимости: mono
Интерактивная: нет
БД: нет
Особенность: специально подготовленные данные

Тестирование


Для тестирования был написан генератор логов, сначала на bash, потом на C++. Было создано три тестовых набора:

  • data_norm — 1000 логов по 2 CPU, один день (~80Mb логов)
  • data_wide — 1000 логов по 2 CPU, один месяц (~2.3Gb логов)
  • data_huge — 10000 логов по 4 CPU, 5 дней (~10Gb логов)

Запросы формировались по принципу:

  • valid — запрос в диапазоне допустимых значений
  • wide — запрос шире, чем допустимые значения (захватывает начало или конец диапазона)
  • invalid — запрос для отсутствующих данных

Все тесты выполнялись по 4 раза, первое значение откидывалось, остальные усреднялись (чтобы исключить время JIT компиляции, прогрева кеша, загрузки из свопа). Тестирование проводилось на рабочем компьютере под Mac OS 10.13.2 с процессором i7 2.2 GHz, 8GB RAM, SSD диском.

Мысли вслух по работе программ
DISCLAIMER: «Прогрев кэша» приводит к тому, что данные подтягиваются в кэш и память и мы измеряем не реальное время отклика, которое было бы у оператора программы, а сферическое в вакууме время отдачи данных из кеша и обработки их в sqlite/python/C#. Это не научно, не профессионально и бесполезно для чего-либо еще кроме этой статьи. Не делайте так в реальной жизни!

К сожалению, тесты по запросу QUERY не очень показательны у половины программ, потому как вывод на экран зачастую в разы дольше самого запроса. В случае программы Nomad1 вывод может занимать сотни миллисекунд из-за очень медленного форматирования в Bash, в то время как запрос выполняется за миллисекунды. В программе Karl вообще допущена ошибка измерения: считается время выполнения внутреннего запроса для QUERY без вывода на экран. В моем понимании «время выполнения команды» это время между вводом команды и получением результата, поэтому к этой программе пришлось применить штрафы, описанные ниже.

Примечательно, что Karl и Alex не сговариваясь написали программы на python 2.7, с использованием SQLite, в интерактивном режиме (сначала загружаются данные, потом принимаются команды). Программа Nomad1 написана на чистом bash как набор CLI скриптов и тоже использует SQLite.

Программы Nomad2 и Nomad3 интересны общим подходом: в случае с Nomad2 все данные грузятся в память в хэш-таблицу с ключом по IP. В случае с Nomad3 условно принимается, что имя файла это IP адрес и при поиске программа просто считывает файл в память и дальше работает перебором. Оба теста актуальны только для сравнения скорости и не участвуют в оценке качества. Кроме всего прочего, они написаны на C#, который представлен на Unix в виде mono и имеет кучу особенностей. Например, результаты mono32 и mono64 разнятся в разы для того же кода, а на Windows и .Net все работает еще быстрее.

Результаты по скорости


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

Запросы
data_norm/valid:
QUERY 10.0.2.23 1 2014-10-31 09:00 2014-10-31 12:00
LOAD 10.0.2.254 0 2014-10-31 13:10 2014-10-31 20:38
STAT 10.0.1.1 2014-10-31 04:21 2014-10-31 08:51

data_norm/wide:
QUERY 10.0.1.11 0 2014-10-01 09:00 2014-10-31 07:21
LOAD 10.0.2.254 1 2014-10-31 15:55 2014-11-04 10:00
STAT 10.0.1.100 2014-10-31 14:21 2015-01-01 01:01

data_norm/invalid
QUERY 10.0.2.23 1 2015-10-31 09:00 2015-10-31 12:00
LOAD 10.0.2.254 0 2015-10-31 13:10 2015-10-31 20:38
STAT 10.0.1.1 2015-10-31 04:21 2015-10-31 08:51

data_wide/valid:
QUERY 10.0.2.33 0 2014-10-30 09:00 2014-10-31 02:00
LOAD 10.0.0.125 1 2014-10-02 14:04 2014-10-04 20:38
STAT 10.0.1.10 2014-10-07 00:00 2014-10-17 23:59

data_wide/wide:
QUERY 10.0.1.11 1 2014-07-30 09:00 2014-10-01 07:21
LOAD 10.0.0.137 0 2014-10-20 04:12 2015-02-01 00:00
STAT 10.0.3.3 2014-10-20 04:12 2015-02-01 00:00

data_wide/invalid
QUERY 10.0.0.123 1 2015-10-31 09:00 2015-10-31 12:00
LOAD 10.0.0.154 0 2015-10-31 13:10 2015-10-31 20:38
STAT 10.0.0.1 2015-10-31 04:21 2015-10-31 08:51

data_huge/valid:
QUERY 10.0.2.33 0 2014-10-30 09:00 2014-10-31 02:00
LOAD 10.0.0.125 1 2014-10-28 14:04 2014-10-30 20:38
STAT 10.0.1.10 2014-10-28 00:00 2014-10-30 23:59

data_huge/wide:
QUERY 10.0.5.72 0 2014-10-31 09:00 2015-11-03 12:11
LOAD 10.0.0.137 0 2014-10-20 04:12 2015-02-01 00:00
STAT 10.0.3.3 2014-10-20 04:12 2015-02-01 00:00

data_huge/invalid
QUERY 10.0.1.11 1 2014-07-30 09:00 2014-10-01 07:21
LOAD 10.0.0.154 0 2015-10-31 13:10 2015-10-31 20:38
STAT 10.0.0.1 2015-10-31 04:21 2015-10-31 08:51

Было сделано 135 тестов (по 27 на каждую программу), их скорость выполнения приведена в таблице:
Test Karl Alex Nomad1 Nomad2 Nomad3
data_norm/valid 0.008800
0.000440
0.000420
0.215300
0.211700
0.217800
0.256200
0.007300
0.008300
0.002160
0.000130
0.000140
0.050200
0.050300
0.052600
data_norm/wide 0.002640
0.000330
0.000630
0.218000
0.212000
0.215000
0.716000
0.008000
0.008600
0.005000
0.000150
0.000320
0.050200
0.005200
0.005500
data_norm/invalid 0.000063
0.000073
0.000065
0.214200
0.209100
0.206300
0.007600
0.008300
0.008100
0.000008
0.000026
0.000034
0.048000
0.053000
0.050000
data_wide/valid 0.007300
0.005500
0.002300
6.237600
6.146500
6.151000
1.446000
0.036000
0.069000
0.017186
0.001099
0.005665
0.167000
0.088000
0.126000
data_wide/wide 0.006800
0.002100
0.024200
6.176600
6.157900
6.326100
0.570000
0.039000
0.070000
0.008363
0.005818
0.005592
0.071000
0.160000
0.159000
data_wide/invalid 0.000085
0.000110
0.000150
6.288100
6.152100
6.130400
0.044000
0.040000
0.062000
0.000013
0.000040
0.000013
0.155000
0.156000
0.164000
data_huge/valid 0.009107
0.007655
0.012858
155.9738
146.5377
140.1752
1.401000
0.013300
0.026000
0.036806
0.003798
0.003751
0.069000
0.066000
0.072000
data_huge/wide 0.009418
0.013718
0.014266
157.1896
148.5435
147.9525
1.078000
0.011700
0.026000
0.018393
0.000805
0.003329
0.072000
0.081000
0.077000
data_huge/invalid 0.000070
0.000095
0.000081
144.7307
158.0090
165.6820
0.012000
0.013000
0.023000
0.000012
0.000031
0.000013
0.054000
0.071000
0.081000

Результат по скорости я оценивал математически: для каждого запроса и набора данных считался порядок (десятичный логарифм от времени в микросекундах) и затем он выступал делителем для порядка самого быстрого решения. Таким образом, самое быстрое решение получало коэффициент 1.0, на порядок более медленное 0.5 и т.д. Результат по каждой программе усредняется и умножается на 40.

$R = 40 \cdot \frac{\sum_{i=1}^{n} \frac{\log_{10}T_{best}}{\log_{10} T_{i} + M_{i}}}{n}$



Для программы Karl, к сожалению, пришлось ввести уменьшающий коэффициент, т.к. она не считала время работы всей команды QUERY, а только внутреннего SQL запроса. Я добавил один порядок (M) ко всем не-пустым результатам QUERY, что уменьшило балл Karl примерно на 2 балла суммарно.

Полную версию таблицы с результатами можно увидеть тут.

Результаты:
Karl: 31/40 (33 без штрафа)
Alex: 15/40
Nomad1: 22/40
Nomad2: 39/40
Nomad3: 21/40

Результаты по качеству


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

Ошибки и недочеты
1. Время в UTC. И Alex и Nomad1 забыли об этом и их результаты смещены на 2 часа из-за нахождения в зоне GMT +2.

2. Индексы. Alex забыл об индексах вообще. Karl создал индекс из всех полей — IP + Timestamp + CPU. Это оправдано только в очень редких случаях поиска по конкретному Timestamp, но по условию задачи мы всегда делаем выборку по IP + CPU и диапазону Timestamp. Это не критично, если размер базы более-менее адекватный, но для вариантов _wide и _huge это привело к огромным потерям памяти при минимуме выигрыша в скорости. Программа Karl на данных _huge постоянно вылетала с ошибкой «Killed: 9» из-за переполнения памяти и свопа.

3. Включение границ диапазона в расчеты. Nomad1 об этом забыл и его выборка из-за каких-то особенностей преобразования timestamp в bash иногда не включает нижнюю границу (в github есть исправленный результат, но в тесты он не вошел).

4. Использование :memory: таблицы с неизвестным объемом данных.
Это архитектурная ошибка и ее допустили Karl и Alex — они сделали in-memory table, не спрашивая себя о последствиях и объемах. В итоге их программы очень зависимы от объема данных и доступной памяти, что уже видно в тесте data_huge. В реальных условиях такие бы программы не работали или работали с проблемами. Идеальный вариант должен оценивать объем считываемых данных и выбирать тип базы.

5. Проверка входящих данных и ошибок. Тут налажали все — запросы в базу не проверяются на валидность даты, адреса, SQL Injection и т.д. В случае invalid запроса LOAD у Alex вылетает ошибка деления на ноль, у Karl пишется No data, а у Nomad1 вообще нет ни одного Exception и вывод ошибки SQLite в запросе STAT будет перемолот через разбиение строки по знаку |. Ни одна программа не воспринимает IP адрес вида 010.00.020.003. Вылеты от неверных запросов были у всех, но т.к. для тестов пришлось сделать 540+ выполнений команд, у меня не хватило здоровья собрать и разобрать их примеры.

6. Округление результатов для LOAD и STAT. Karl ничего не округлял и вывел число с десятичной точкой, что не смертельно, но не соответствует условию задачи. Alex привел число к INT, отбросив дробную часть целиком.

Все три программы написаны на современных и читабельных языках программирования (VBScript и Brainfuck не замечено). Код на bash чуть менее читабелен, чем версии на Python, но заметно меньше по объему. Код Alex использует сторонние библиотеки readline и progress, написан свой класс для Auto-complete по Tab, есть отдельные функции для хелпов, работы с датой, поддержка перезагрузки данных, обработка ошибок, однако база не закрывается при выходе. Код Karl использует класс для наследования от Cmd, обработку исключений, закрывает БД при выходе, ловит Ctrl-C. К сожалению, комментариев нет ни у кого (с парой не существенных исключений).
Интересный и более программистский подход использует Alex — он для всех трех команд делает одинаковый запрос, а затем в коде считает данные для STAT/LOAD, не пользуясь AVG и GROUP BY. Это существенно снижает объем кода, а скорость выполнения в целом получается такая же, как если переложить эту задачу на БД.

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

Karl: 35/60
Alex: 40/60
Nomad1: 30/60

Выводы


Сумма баллов:
Karl: 66/100
Alex: 55/100
Nomad: 52/100

По баллам и скорости всех обошло решение Karl, потому как решение Alex не конкурентно по скорости из-за отсутствия индексов. Что интересно, как только я сообщил Alex про невысокую скорость, он сказал, что в 82й строке можно добавить индексы, он это планировал и продумал, но решил оставить «на потом». К сожалению, это было уже было после приема программ и заморозки кода, поэтому такое изменение внести было нельзя.

Программы Nomad2 и Nomad3 набрали по скорости 39/40 и 21/40 балла соответственно. Не удивительно, что работа с хеш-таблицей оказалась быстрее БД, пусть и с большими потерями памяти. Работа напрямую с файловой системой оказалась не особо быстрой, но надо понимать, что у такого варианта почти отсутствует время инициализации, у него минимальная нагрузка на память и по большому счету он может использоваться с любыми объемами заранее подготовленных данных.

Вариант Karl за счет «широкого» индекса потреблял больше всех памяти и падал уже при размере данных в 6Гб. Все варианты с :memory: таблицей или хэш-таблицами не смогут работать при объемах 10Гб и выше, в то время как решение с БД в файле не намного медленнее и масштабируется гораздо лучше. К сожалению, вывод данных через bash поставил крест на скорости этой программы.

Работа в виде интерактивных приложений дает существенный прирост к скорости — у программ Nomad1 и Nomad3 явно видно, что даже на пустых запросах около 10 мс для bash и 50 мс для C# уходит только на запуск.

Субъективная оценка


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

Все три участника использовали SQLite и не стали городить свой велосипед. Это несомненный плюс, но и явно показывает, что все три варианта далеки от чистого программирования. Они решают свою задачу, при чем, достаточно быстро, но без попыток создать собственную In-Memory Database с быстрой индексацией (как в варианте Nomad2) или выборками без предварительной загрузки (как в варианте Nomad3). Чуть-чуть ближе к программерскому решению подход Alex к использованию единого запроса, а потом расчетах LOAD/STAT в коде. Так же я не увидел в коде других «спутников программиста», таких как логи, комментарии, собственные структуры для данных (адрес в IP4 ведь это 32-битное число, а CPU и LOAD — однобайтовые переменные!). Авторы в целом не стали задумываться о хранении данных и по большому счету просто сделали перенос текстовых файлов в бинарный формат SQLite.

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

Самое «админское» решение:

1. Nomad1 — это и команда .import, и передача данных в консольный клиент sqlite вместо коннектора/курсора
2. Karl — работа с индексами, SQL запросы для всех операций, GROUP BY, ORDER BY
3. Alex

Самое «программерское» решение (перк «он создал новый инструмент»):

1. Alex — хорошая структура, работа с массивом данных при выборке, сторонние библиотеки
2. Karl — код c исключениями, очистка данных
3. Nomad1

Самое «универсальное» решение:

1. Nomad1 — команды дописываются отдельными файлами-запросами к готовой БД по аналогии с имеющимися; программа не зависит от объема данных и памяти.
2. Alex — единый запрос выдает массив данных, дальше код их обрабатывает; в шапке файла есть код для работы с файловой БД
3. Karl

Все коды программ, включая генератор, доступны на GitHub

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

P.S.: По итогам тестов Nomad1 — программист с 20-летним стажем — получил меньше баллов в данной задаче, чем DevOps и Junior developer. С другой стороны, он еще и автор статьи и было бы, кхм, не корректно присуживать себе более высокие баллы :)

P.P.S.: Написание статьи и выполнение замеров потребовало три рабочих дня, это больше, чем все участники вместе взятые потратили на написание и отладку кода. Производительность автора как писателя — однозначно неудовлетворительная.

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


  1. RouR
    27.05.2018 16:06

    поэтому не удивительно, что все три варианта используют SQLite

    У меня сложилось впечатление что админы не знают про prometheus, про существование отдельного вида БД — Time-Series Database (который используется в prometheus). Если прям так необходимо написать своё решение руками, то для задачи можно выбрать или OpenTSDB, или InfluxDB, или еще какую-нибудь БД, такого же типа.


    1. Nomad1 Автор
      27.05.2018 16:16

      В условии используется стандартная ловушка подобных тестов: говорится, что к примеру, у нас есть 1000 серверов по 2CPU и они создают по 1440 записей за день. И сразу человек начинает думать, что этим все и ограничится, нужно решение для работы всего с 3 миллионами записей, это очень мало и все можно сделать весьма быстро. Хотя никто и не смотрит, что постановка расплывчата и данных может быть намного больше. Я думаю, если бы было сказано о миллиардах записей в сотнях гигабайт логов, то никто бы на SQLite и не смотрел, пропала бы вся интрига.


      1. Graf54r
        29.05.2018 08:55

        это игра из разряда «Детектив». Вы пишите детективный роман, перечисляете улики и предоставляете читателю догадаться о том, какой же садовник убийца. А перед раскрытием добавляете, что это оказался Том, сигаретный окурок которого был заслонен телом убитого. Поменять условие в любую сторону — это же так умно!
        Просить сделать проект дачного домика, а потом спрашивать почему фундамент такой маленький, ведь я планирую тут построить небоскреб.


        1. Nomad1 Автор
          29.05.2018 08:58

          Пассаж с детективом — без комментариев.
          С дачным домиком все верно, правда в плане звучало «все 1000 жильцов домика», человек должен был догадаться, что что-то тут не так.


      1. Nikobraz
        29.05.2018 09:02

        Это не проблема того, кто пишет код.
        Это проблема того, кто ставит задание.


    1. SirEdvin
      28.05.2018 00:20
      +1

      Вот только есть одна проблема — значительное количество Time-Series Database имеют проблему со вставкой назад. Например, в prometheus это сделать нормальными способами нельзя, насколько я знаю. Ну и с ними надо работать, я вот работаю с prometheus, но в этой задаче он помочь не может. А sqlite знают почти все.


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


  1. Codewaves
    27.05.2018 21:27

    Как вариант, пропарзить все данные, отгрупировать по ип-процессор и создать чисто таймлайн загруженности процессора, 1 байт на 1 минуту/проц, за день набежить около 3мб, за год 1гб. А если еще точность не очень важна, можно еще и запаковать.


    1. JekaMas
      28.05.2018 14:14

      Olap cube? И любую статистику по срезам делать.


    1. Akon32
      28.05.2018 16:47

      А если еще точность не очень важна, можно еще и запаковать.

      Полагаю, при схеме "дельта-кодирование + lzma" (если загрузка ритмична или более-менее постоянна) можно получить дичайшее сжатие (в ~1000 раз) даже без потери точности (если загрузка процессора кодируется целым значением).


      1. Codewaves
        28.05.2018 20:34

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


        1. Nomad1 Автор
          28.05.2018 20:54

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


  1. mk2
    27.05.2018 21:27

    Еще одно очень большое допущение — что сервера всё время исправно работают, и логи исправно создаются.

    Если бы не команда QUERY, имело бы смысл делать препроцессинг, считать частичные суммы и прочие программистские оптимизации. Но с ней (и учитывая, что 1 секунду будут измерять по ней) смысл имеет только как-то похранить данные. А для этого имхо лучше воспользоваться стандартным решением под данные нужды — упомянутыми выше RouR Time-Series Database или любой другой БД, если о них пишущий не знает.

    P.S. Pyhon поправьте))


    1. Ztare
      28.05.2018 10:35

      Почему же можно было делать оптимизации с группировкой за год/за месяц/за день/за час и тд
      А потом агрегировать данные разных группировок в случае полного попадания в запрошенный диапазон. К примеру просят 1год 3 месяца 4часа значит если год четко полный попал в диапазан — берем из индекса годов, что осталось смотрим целые месяцы и т.п.
      Но в целом создание индексов это читерство, мы переносим время исполнения запроса в прошлое и обманываем замер скорости


      1. mayorovp
        28.05.2018 11:34

        А в чем обман-то? Индекс создается один раз, а потом используется сколько угодно.


        1. Ztare
          28.05.2018 12:59

          Он не один раз создается, его поддерживать нужно. Но в целом вы наверное правы, поторопился с выводами, программа то для многоразового использования, а не для одного вызова.


  1. puyol_dev
    28.05.2018 10:22
    -2

    Довольно много таких «теоретических» статей на этом ресурсе. Есть стойкое ощущение от них, что их авторам просто занять себя нечем


    1. Nomad1 Автор
      28.05.2018 10:24
      +2

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


      1. puyol_dev
        28.05.2018 12:28

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


        1. Nomad1 Автор
          28.05.2018 12:47

          Будут пытаться писать статьи, бенчмарки, учиться использовать базы данных, читать о time-series DB, изучать разные точки зрения в комментариях? И это по-вашему вредно? Позвольте выразить мое полное несогласие с данной мыслью.
          На мой взгляд, вредно будет, лишь если кто-то решит подобные тестовые решения использовать в продакшене, но как бы, если у человека настолько выражена проблема с критичным мышлением, то он споткнется о любые грабли в любом месте.


          1. puyol_dev
            29.05.2018 10:58
            +1

            Собственно сами и подтверждаете, что ваша статья лишь для абстрактного «саморазвития», с реальными задачами мало пересекается


            1. Nomad1 Автор
              29.05.2018 11:26

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


              1. puyol_dev
                29.05.2018 11:58
                +2

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


                1. Nomad1 Автор
                  29.05.2018 12:05

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


  1. GoodGod
    28.05.2018 11:25

    Можно создать индекса по полю: год-месяц-день-час от поля timestamp. Тогда и память не будет сильно тратится и скорость может стать быстрее.


    1. GoodGod
      28.05.2018 11:32

      Точнее добавить в индекс.


      1. Nomad1 Автор
        28.05.2018 12:51

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


  1. Germanets
    28.05.2018 11:43

    Мне кажется, наиболее «инженерным» подходом в решении данной задачи было бы предложить отказаться от записи логов в файл, переведя всё это на какую-то более-менее подходящую СУБД, а заодно ещё уточнить(и предложить свои?) способы использования и максимальное требуемое хранение данных. Вариант «мы тут каждую минусу создаём кучу файлов и для выборки их каждый раз долго и мучительно парсим», какой-то уж больно подозрительный.


    1. Nomad1 Автор
      28.05.2018 12:57

      Это и называется «задача для собеседования» — описать какую-то адскую ситуацию, которую нельзя исправить, но надо ее направить в разумное русло. Вот вам схожий вариант, о котором я когда-то слышал на одном из форумов: все access.log апачей с двух десятков веб-серверов пишутся на NFS шару, где должны складироваться и анализироваться.


  1. Bakanych
    28.05.2018 12:42

    Просто интересно, а почему не elastic search, например, или что-то подобное из готовых решений?


    1. Nomad1 Автор
      28.05.2018 13:59

      А разве elastic search подойдет для обработки структурированных данных? Это же не поиск по тексту, а скорее работа с массивом. Попробуете сделать свой вариант с ним? )


  1. GamePad64
    28.05.2018 14:43
    +1

    А вот в такой ситуации не надо придумывать велосипеды, а нужно развернуть ELK, и настроить Logstash на сбор логов из директорий.
    Благодаря Docker-образам и большому количеству мануалов с примерами в сети, ELK можно развернуть примерно за пару часов и это будет работать отлично.


    Сразу отвечу на комментарий Nomad1 выше: да, Elasticsearch нормально прожуёт структурированные данные и более того, в Kibana можно будет получить хорошую аналитику из этих данных.


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


    1. nonname
      28.05.2018 16:33

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


      1. GamePad64
        28.05.2018 17:20
        +2

        Та задача, что в статье — это one-off task. Хороший разработчик будет сразу исходить из этого факта и решит задачу максимально быстро. При этом допустим и плохой код, и медленный, пока он выполняется за разумное время. Измерять время работы кода тут бессмысленно, задача-то одноразовая, без разницы, выполнится она за три минуты или за секунду.


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


        1. nonname
          28.05.2018 17:47
          +2

          Вы наверное не совсем поняли смысл такой задачи. Мне как раз недавно довелось проводить собеседования на DevOps'а и тут речь вообще не про архитектуру хранения логов. Просто вот сидит перед вами человек, он 10 лет занимался opsом в эксплуатации и процесс погружения в DevOps может быть только в самом начале. В резюме у него написано «программирую на Python», в гитхабе его нет. Нужно хоть как-то оценить навыки программирования, а они, поверьте, бывают на уровне от «не могу split'ом поделить строку» до «написание собственного CI\CD инструментария с RESTful API». Вот на таких незамысловатых тестах проверяется вообще способность строить алгоритмы, хоть и способ оценки по скорости работы, выбранный автором статьи, у меня тоже вызывает вопросы.


    1. chupasaurus
      28.05.2018 17:37

      «Жевать» структурированные данные будет Logstash, Elasticsearch получит уже разложенные в JSON данные с типами.


  1. MShevchenko
    28.05.2018 21:55
    +1

    Ой как знакомо. Двадцать лет назад (да, в 1998-ом) я столкнулся с очень похожей задачей. С тех пор иногда люблю ее задавать на собеседованиях по SQL.
    Постановка задачи:
    Мы интернет провайдер раздающий dial-up. У нас есть, скажем, 128 входных модемов объединенных в hunting группы по 16. Cisco 2511 помните? ;-)
    Каждый модем может быть либо занят клиентом либо свободен. Время нахождения клиента на линии не может быть более 3-х часов.
    Информация об этом сохраняется таблицу MySQL в следующем виде:
    Timestamp, ID терминала, ID клиента, Время занятия линии в секундах.
    Информация хранится несколько месяцев.

    Требуется построить почасовой суточный график загрузки всего пула с помощью одного SQL запроса. Без сохраненных процедур и view. При этом нужно учитывать какой процент времени в течении каждого часа модем был свободен, пользователь мог прийти позавчера и отсоединится вчера и так далее. Так же нужно учитывать что этот запрос должен отрабатывать в разумное время на SPARCStation 5 c 70MHz процессором. ;-)


    1. JekaMas
      28.05.2018 22:51
      +3

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


      1. MShevchenko
        29.05.2018 23:53
        +1

        Зачем? ;-) Абитуриент должен быть напуган. ;-)


        1. JekaMas
          30.05.2018 00:15
          +1

          Оооо да!!!
          Такая задача напугает и профессионала.