Инструменты командной строки могут быть в 235 раз быстрее, чем кластер Hadoop

KbrnttXdTYQ_1

Создание конвейера данных из команд оболочки подобно обладанию собственным кластером Storm на локальном компьютере. В конвейеры оболочки переносятся как концепции Spouts, Bolts и Sinks, так и команды. С помощью основных команд вы можете с легкостью построить конвейер потоковой обработки , который будет иметь чрезвычайно высокую производительность по сравнению со многими современными Big Data инструментами.

Также хотелось бы отметить ещё один аспект использования пакетного подхода против потокового при анализе: если загружать большое количество данных (например, 10000 партий в шахматы) и проводить анализ локально, то для выполнения этой операции остается очень мало памяти. Происходит это потому, что все данные для анализа загружаются в оперативную память. Однако, эта проблема легко решается использованием потокового анализа, который практически не требует затрат памяти. В результате конвейер потоковой обработки, который мы создадим, будет работать в 80 раз быстрее, чем реализация Hadoop, и практически не использовать память.

Изучение данных

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

[Event "F/S Return Match"]

[Site "Belgrade, Serbia Yugoslavia|JUG"]

[Date "1992.11.04"]

[Round "29"]

[White "Fischer, Robert J."]

[Black "Spassky, Boris V."]

[Result "1/2-1/2"]

(moves from the game follow...)

Мы заинтересованы только в результатах игры, которые имеют только 3 реальных исхода. Результат 1–0 означает, что белые выиграли, результат 0–1 — победили черные, и вариант 1/2–1/2 — игра прошла вничью. Существует также α-вариант, означающий, что игра проходит в настоящее время или не может быть засчитана, но мы игнорируем это для достижения своих целей.

Получение образца данных

Первое, что нужно сделать — получить большое количество игровых данных. На GitHub размещен репозиторий от rozim, в котором есть много игр. Можно воспользоваться им, чтобы составить набор данных объемом 3.46GB. Следующий шаг — получить все эти данные в нашем конвейере.

Построение конвейера обработки

Если вы следите и синхронизируете обработку, не забудьте очистить кэш страницы ОС, так как в противном случае вы получите недопустимое время обработки.

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

1 sleep 3 | echo "Hello World."

Интуитивно может показаться, что программа будет приостановлена на 3 секунды, а затем распечатает «Hello World», но на самом деле оба шага будут выполняться одновременно. Именно этот факт позволяет получать большие ускорения для простых не IO-связанных систем обработки, способных работать на одной машине.

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

В этом случае, анализ 3.46GB данных займет около 13 секунд, что составляет около 272MB / сек. Это своего рода верхняя граница, показывающая, насколько быстро могут быть обработаны данные в системе из-за IO ограничений.

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

1 cat * .pgn

Если вас заинтересуют результаты, то можно просто просканировать все файлы данных и выбрать строки, содержащие «Results», с помощью grep .

1 cat * .pgn | grep «Results»

Это позволит провести выборку из файлов строк с «Result» . Теперь, если мы хотим, то можем просто использовать sort и uniq команды, чтобы получить список всех уникальных строк в файле вместе с их полями.

1 cat * .pgn | grep «Results» | sort | uniq -c

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

Для того, чтобы уменьшить затраты времени, мы можем убрать шаги sort | uniq из конвейера и заменить их AWK, который является прекрасным инструментом/языком для обработки данных, представляющих собой результаты мероприятий/игр.

1 cat *.pgn | grep "Result" | awk '{ split($0, a, "-");
res = substr(a[1], length(a[1]), 1);
if (res == 1) white++;
if (res == 0) black++;
if (res == 2) draw++;}
END
{ print white+black+draw, white, black, draw }'

Алгоритм возьмет каждую запись, содержащую результат, разделит её на подстроки, и немедленно вернет 0 в случае выигрыша черных, 1 — в случае выигрыша белых или 2 — в случае ничьей. Обратите внимание, что $0 является встроенной переменной, которая представляет всю запись.

Этот вариант сокращает время работы приблизительно до 65 секунд, а так как мы обрабатываем в два раза больше данных происходит ускорение примерно в 47 раз.

Таким образом, в этот момент у нас уже есть уменьшение временных затрат примерно в 47 раз благодаря простому локальному решению. Кроме того, использование памяти равно почти нулю, поскольку учитываются только хранящиеся данные, и увеличение 3 целых чисел практически не занимает места в памяти. Однако, анализ htop во время работы показывает, что в настоящее время при полном использовании одного ядра процессора узким местом является grep.

Распараллеливание узких мест

Проблема неиспользованных ядер может быть устранена с помощью замечательной команды xargs, которая позволит нам распараллелить grep. Так как xargs ожидает ввода определенным способом, надежнее и проще использовать find с аргументом -print0 для того, чтобы убедиться, что каждое имя файла, передаваемое в xargs, имеет завершающий ноль. Соответствующий -0 говорит xargs ожидать данные с завершающим нулем. Кроме того, -n показывает сколько входных данных передавать каждому процессу и -P указывает на количество процессов, которые могут работать параллельно. Также важно знать о том, что такой параллельный конвейер не гарантирует порядок доставки, но это не проблема, если вы привыкли иметь дело с распределенными системами обработки. -F для grep означает, что мы только сопоставляем фиксированные строки, а не какие-либо желаемые регулярные выражения, и может предложить небольшой прирост скорости, который при тестировании практически незаметен.

1 find . -type f -name '*.pgn' -print0 | xargs -0 -n1 -P4 grep -F "Result" | gawk '{ split($0, a, "-");
res = substr(a[1], length(a[1]), 1);
if (res == 1) white++;
if (res == 0) black++;
if (res == 2) draw++;}
END { print NR, white, black, draw }'

В результате время выполнения уменьшается примерно до 38 секунд, что является дополнительным сокращением времени обработки на 40% при распараллеливании grep в нашем конвейере. Т.е. этот метод работает примерно в 77 раз быстрее, чем реализация Hadoop. Хотя мы улучшили производительность за счет распараллеливания grep, мы, на самом деле, можем полностью его удалить, имея AWK-фильтр входных записей (в данном случае — строк) и работая только с теми строками, которые содержат подстроку «Результат».

1 find . -type f -name '*.pgn' -print0 | xargs -0 -n1 -P4 awk '/Result/ { split($0, a, "-");
res = substr(a[1], length(a[1]), 1);
if (res == 1) white++;
if (res == 0) black++;
if (res == 2) draw++;}
END { print white+black+draw, white, black, draw }'

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

1 time find . -type f -name '*.pgn' -print0 | xargs -0 -n4 -P4 awk '/Result/ { split($0, a, "-");
res = substr(a[1], length(a[1]), 1);
if (res == 1) white++;
if (res == 0) black++;
if (res == 2) draw++ } END { print white+black+draw, white, black, draw }' | awk '{games += $1;
white += $2;
black += $3;
draw += $4; } END { print games, white, black, draw }'

При добавлении второго шага AWK  в конце, мы, как и хотели, получаем суммарную информацию об игре.

Этот способ еще больше повышает скорость, уменьшив затраты до 18 секунд, что примерно в 174 раза быстрее, чем реализация Hadoop.

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

1 find . -type f -name '*.pgn' -print0 | xargs -0 -n4 -P4 mawk '/Result/ { split($0, a, "-");
res = substr(a[1], length(a[1]), 1);
if (res == 1) white++;
if (res == 0) black++;
if (res == 2) draw++ }
END { print white+black+draw, white, black, draw }' | mawk '{games += $1;
white += $2;
black += $3;
draw += $4; } END { print games, white, black, draw }'

Такой find | xargs mawk | mawk конвейер позволяет нам снизить время выполнения до 12 секунд, или около 270MB / сек, что примерно в 235 раз быстрее, чем реализация Hadoop.

Вывод

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

Перевод статьи: «Command-line tools can be 235x faster than your Hadoop cluster»