Анализ больших данных: лучший способ хранения и запроса данных сигнала

Я делаю анализ сигналов с помощью Hadoop/Spark, и мне нужна помощь в том, как структурировать весь процесс.

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

<Measure ID> <Source ID> <Measure timestamp> <Signal values>

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

000123  S001  2015/04/22T10:00:00.000Z  0.0,1.0,200.0,30.0 ... 100.0
000124  S001  2015/04/22T10:05:23.245Z  0.0,4.0,250.0,35.0 ... 10.0
...
000126  S003  2015/04/22T16:00:00.034Z  0.0,0.0,200.0,00.0 ... 600.0

Мы хотели бы написать интерактивные/пакетные запросы:

применять функции агрегации по значениям сигнала

SELECT *
FROM SIGNALS
WHERE MAX(VALUES) > 1000.0

Чтобы выбрать сигналы с пиком более 1000,0.

применить агрегацию по агрегации

SELECT SOURCEID, MAX(VALUES) 
FROM SIGNALS
GROUP BY SOURCEID
HAVING MAX(MAX(VALUES)) > 1500.0

Чтобы выбрать источники, имеющие хотя бы один сигнал, превышающий 1500,0.

применять определенные пользователем функции над образцами

SELECT *
FROM SIGNALS
WHERE MAX(LOW_BAND_FILTER("5.0 KHz", VALUES)) > 100.0)

чтобы выбрать сигналы, которые после фильтрации на 5.0 кГц имеют по меньшей мере значение более 100.0.

Нам нужна помощь, чтобы:

  • найдите правильный формат файла для записи данных сигналов на HDFS. Я подумал об Apache Parquet. Как бы вы структурировали данные?
  • понять правильный подход к анализу данных: лучше создавать разные наборы данных (например, обрабатывать данные с помощью Spark и сохраняющиеся результаты на HDFS) или пытаться делать все в время запроса из исходного набора данных?
  • Является ли улов хорошим инструментом для создания таких запросов, которые я написал? Мы работаем на Cloudera Enterprise Hadoop, поэтому мы также можем использовать Impala.
  • В случае, если мы создаем другой исходный набор данных из исходного, как мы можем отслеживать происхождение данных, то есть знать, как данные были сгенерированы из исходной версии?

Большое спасибо!

Ответ 1

1) Паркет в виде столбчатого формата подходит для OLAP. Спарковая поддержка паркета достаточно зрелая для использования в производстве. Я предлагаю проанализировать строку, представляющую значения сигнала, в следующую структуру данных (упрощенную):

 case class Data(id: Long, signals: Array[Double])
 val df = sqlContext.createDataFrame(Seq(Data(1L, Array(1.0, 1.0, 2.0)), Data(2L, Array(3.0, 5.0)), Data(2L, Array(1.5, 7.0, 8.0))))

Сохранение массива double позволяет определять и использовать UDF следующим образом:

def maxV(arr: mutable.WrappedArray[Double]) = arr.max
sqlContext.udf.register("maxVal", maxV _)
df.registerTempTable("table")

sqlContext.sql("select * from table where maxVal(signals) > 2.1").show()
+---+---------------+
| id|        signals|
+---+---------------+
|  2|     [3.0, 5.0]|
|  2|[1.5, 7.0, 8.0]|
+---+---------------+

sqlContext.sql("select id, max(maxVal(signals)) as maxSignal from table group by id having maxSignal > 1.5").show()
+---+---------+
| id|maxSignal|
+---+---------+
|  1|      2.0|
|  2|      8.0|
+---+---------+

Или, если вы хотите некоторую безопасность типа, используя Scala DSL:

import org.apache.spark.sql.functions._
val maxVal = udf(maxV _)
df.select("*").where(maxVal($"signals") > 2.1).show()
df.select($"id", maxVal($"signals") as "maxSignal").groupBy($"id").agg(max($"maxSignal")).where(max($"maxSignal") > 2.1).show()
+---+--------------+
| id|max(maxSignal)|
+---+--------------+
|  2|           8.0|
+---+--------------+

2) Это зависит: если размер ваших данных позволяет выполнять всю обработку во время запроса с разумной задержкой - идите на это. Вы можете начать с этого подхода и создать оптимизированные структуры для медленных/популярных запросов позже

3) Улей медленный, он устарел от Impala и Spark SQL. Выбор нелегкий, мы используем эмпирическое правило: Impala хороша для запросов без объединений, если все ваши данные хранятся в HDFS/Hive, Spark имеет большую задержку, но соединения надежны, он поддерживает больше источников данных и имеет богатую обработку, отличную от SQL (например, MLlib и GraphX)

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

Надеюсь, что это поможет

Ответ 2

Во-первых, я считаю, что подход Виталия очень хорош во всех аспектах. (и я все для Spark)

Я бы хотел предложить другой подход. Причины таковы:

  • Мы хотим сделать интерактивные запросы (+ у нас есть CDH)
  • Данные уже структурированы.
  • Необходимо "анализировать" и не совсем "обрабатывать" данные. Spark может быть чрезмерным, если (a) структурированы данные, мы можем быстрее создавать SQL-запросы и (b) мы не хотим писать программу каждый раз, когда хотим запустить запрос

Ниже перечислены шаги, которые я хотел бы выполнить:

  • Проглатывание с использованием sqoop для HDFS: [необязательно] использование --as-parquetfile
  • Создайте таблицу внешнего Impala или внутреннюю таблицу, как вы пожелаете. Если вы не передали файл как файл паркета, вы можете сделать это во время этого шага. Разделяйте, предпочтительно, ID источника, так как наши группировки будут происходить в этом столбце.

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

Перемещение данных:  - если нам нужно генерировать фид из результатов, создайте отдельный файл  - если другая система собирается обновлять существующие данные, а затем перемещать данные в другое место при создании- > загрузке таблицы  - если речь идет только о запросах и анализе и получении отчетов (т.е. внешних таблиц достаточно), нам не нужно перемещать данные без необходимости  - мы можем создать таблицу внешних улей поверх одних и тех же данных. Если нам нужно запустить длительные пакетные запросы, мы можем использовать Hive. Тем не менее, это не-нет для интерактивных запросов. Если мы создадим любые производные таблицы из запросов и хотим использовать через Impala, не забудьте запустить "invalidate metadata" перед запуском запросов impala в таблицах сгенерированных улей

Lineage - я не углублялся в это, здесь ссылка на Impala lineage с использованием Cloudera Navigator