Побег от скуки — процессы ETL

в 14:24, , рубрики: Без рубрики

В конце зимы и начале весны, появилась возможность поработать с новым для меня инструментом потоковой доставки данных Apache NiFi. При изучении инструмента, все время не покидало ощущение, что помимо официальной документации, нелишним были бы материалы "for dummies", с практическими примерами.

После выполнении задачи, решил попробовать облегчить вхождение в мир NiFi.

Предыстория, почти не связанная со статьей

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

Система Apache NiFi была выбрана на удачу. Прототип был построен и сдан заказчику.

Первоначально заказчик хотел монолитное приложение, а использование NiFi рассматривал просто как инструмент прототипирование (где-то прочитал). Но после знакомства в вблизи — NiFi остался в продукте.

А теперь собственно история

После сдачи работы заказчику, интерес к ETL не пропал. В апреле наступила самоизоляция с нерабочими днями и свободное время нужно было потратить с пользой.

Когда я начал разбираться с Apache NiFi выяснилось, что более-менее подробная информация есть только на сайте проекта. Русские статьи во многом просто переводы вводных частей официальной документации. Основной недостаток — часто не понятно в каком формате параметры вводить. Гугл спасает, но не всегда.

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

И так, задача — получать данные о распространении вируса, обрабатывать строить графики.
Источниками данных будет сайты “стопкороновирус.рф» и «covid19.who.int». Первый сайт содержит данные по регионам России, сайт ВОЗ — данные по странам мира.

Данные на сайте «стопкороновирус.рф» находятся прямо в html-коде страницы в виде почти готового json-массива. Нужно его только обработать. Но об этом в следующей статье.
Данные ВОЗ находятся в csv-файле, который не получилось автоматически скачивать (либо я был недостаточно настойчив). Поэтому, когда нужно было, файл сохранялся из браузера в специальную папку.

Если кратко сказать о NiFi — система при выполнении процесса (pipeline), состоящего из процессоров, получает данные, модифицирует и куда-то сохраняет. Процессоры выполняют работу — читают файлы, преобразовывают один формат в другой, обогащают данные из другой базы данных, загружают в хранилища и т.д. Процессоры между собой соединены очередями. У каждой очереди есть признак, по которому данные в нее загружаются из процессора. Например в одну очередь можно загрузить данные при удачной выполнении работы процессора, в другую при сбое. Это позволяет запустить данные по разным веткам процессов. Например, берем данные, ищем подстроку — если нашли отправляем по ветке 1, у тех данных, в которых подстрока не найдена — отправляем по ветке 2.

Каждый процессор может находиться в состоянии "Running" или "Stopped". Данные накапливаются в очереди перед остановленным процессором, после старта процессора, данные будут переданы в процессор. Очень удобно, можно изменять параметры процессора, без потери данных на работающем процессе.

Данные в системе называются FlowFile, потоковый файл. Процесс начинает работать с процессора, который умеет генерировать FlowFile. Не каждый процессор умеет генерировать потоковый файл — в этом случае в начало ставится процессор "GenerateFlowFile", задача которого создать пустой потоковый файл и тем самым запустить процесс. Файл может создаваться по расписанию или событию. Файл может быть бинарным или текстовым потоком.

Файл состоит из контента и метаданных. Метаданные называются атрибутами. Например, атрибутом является имя файл, id-файла, имя схемы и т.д. Система позволяет добавлять собственные атрибуты, записывать значения. Значения атрибутов используются в работе процессоров.

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

В NiFi еще одна важная сущность — контроллеры. Это объекты которые знают как сделать какую-то работу. Например, процессор хочет записать в базу данных, то контроллер будет знать, как найти и подключиться к базе данных, таблице.

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

Итак практическое применение вольного изложения документации — чтение данных ВОЗ и сохранение в БД. Данные будем хранить в СУБД PostgreSQL.

Создаем таблицу:

CREATE TABLE public.who_outbreak (
    dt timestamp NULL,
    country_code varchar NULL,
    country varchar NULL,
    region_code varchar NULL,
    died int4 NULL,
    died_delta int4 NULL,
    infected int4 NULL,
    infected_delta int4 NULL
);

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

Источником данных будет cvs-файл с сайта https://covid19.who.int/ по кнопке “Download Map Data”. Файл содержит информацию по заболевшим и погибшим по всем странам на каждый день примерно с конца января. Оперативная информация там задерживается на 1-2 дня. За это время в файле менялись наименования полей (были даже наименования с пробелами), менялся формат даты.

Файл сохраняется из браузера в определенный каталог, откуда NiFi забирает его на обработку.

image
Общий вид визуализации процесса в интерфейсе Apache NiFI

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

Обработка начинается с верхнего процесса, отходящие от процесса стрелочки показывают направление движения FlowFile (атрибутов и контента).

На визуализации процесса показывается его статус (работает или нет), данное имя процессу, его тип, количество и общий объем прошедших файлов на входе и на выходе за период времени, в данном случае за 5 мин.

Визуализация очереди показывает ее имя, и объем данных в очереди. По умолчанию имя очереди это тип связи — success, failed и другие.

Тип первого процесса "GetFile". Этот процессор создает flowfile и запускает процесс. Контентом потокового файла будет содержимое файла — если он будет найден. В настройках процессора на вкладке Scheduling указываем расписание запуска процесса — 20 секунд.

Побег от скуки — процессы ETL - 2
Вкладка Scheduling — запуск каждые 20 сек.

После старта процессора, каждые 20 секунд процессор будет запускаться. Если файл будет найден — FlowFile будет создан и процесс запустится.

Как видно из рисунка, указываем каталог и имя файла. В имени файла можно использовать символы подстановки. Например «*.csv» приведет к обработке всех csv-файлов в каталоге. Указываем также, что после обработки файл можно удалить ("Keep Source File"). Также есть возможность указать максимальные и минимальные значения возраста и размера файла. Это позволяет обрабатывать, например, только не пустые файл, созданные за последний час.
На вкладке Settings указываются базовые параметры процесса, такие как имя процесса, максимальное время работы процесса, время между запусками, типы связей.

Результатом работы первого процесса "GetFile" с именем "Read WHO datafile" будет просто поток данных из файла. Поток будет передан в следующий процесс "ReplaceText".

Побег от скуки — процессы ETL - 3
Процессор поиска подстроки

В этом процессе обратим внимание сразу на вкладку параметров. Данный процессор ищет regex-выражение "Search Value" в входном потоке и заменяет на новое значение "Replacement Value". Обработка ведется построчно ("Evaluation Mode"). В данном случае идет замена в строке даты. Во входном файле, в какой-то момент дата формата YYYY-MM-DD стала указываться как YYYY-MM-DDThh:mm:ssZ, причем время было всегда 00:00:00, а временная зона не указывалась.
Простого способа преобразования в даты уже в записи не нашел, поэтому к проблеме подошел в “лоб” — просто через процессор "ReplaceText" убрал символы T и Z. После этого строка стала конвертироваться в timestamp в avro-схеме без ошибок.

На выходе процессора будет поток текстовых данных, в которых уже поправили подстроку даты. Но пока это просто поток байтов без какой-то структуры.

Следующий процессор "Rename fields" читает поток уже как структурированные данные.

Побег от скуки — процессы ETL - 4
Переименование полей

Процессор содержит ссылку на Reader – специальный объект-контроллер, который умеет читать из потока структурированные данные и в таком виде уже передает процессору на обработку. В данном случае "WHO CVS Reader” просто читает поток и преобразует каждую строку cvs-файла в запись (record) которая содержит поля со значениями из строки. Имена полей берутся из заголовка cvs-файла.

Побег от скуки — процессы ETL - 5
Контроллер чтения записей из cvs-файла

Параметр "Schema Access Strategy" указывают, что структура записи формируется из заголовка cvs-файла. Если заголовков нет, то можно изменить стратегию доступа к схеме и в реестре схем данных создать схему, указать ее имя в параметре "Schema Name" или еще проще — указать саму схему в параметре "Schema Text".

Но так как у нас есть заголовки в файле — читаем по ним.

Итак, в процессоре данные уже структурированные — их можно представить как таблицу базы данных — поля и строки. И к этой таблице мы выполняем SQL запрос :

select
    Date_reported dt,
    Country_code country_code,
    Country country,
    WHO_region region_code,
    New_deaths died_delta,
    Cumulative_deaths died,
    New_cases infected_delta,
    Cumulative_cases infected
from FLOWFILE

Поля в запросе такие же как заголовки cvs-файла. Имя таблицы служебное — FLOWFILE – обозначает чтение структурированных данных их контента файла. Язык запроса SQL довольно гибкий, есть функции преобразований, агрегаций и т.д. В данном случае запрос выводит все данные, только имена полей результата будут другие — они соответствуют полям таблицы who_outbreak в целевой БД.

Поток записей с новыми именами полей передается в контроллер RecordSetWriter, ссылка на который также указана в параметра контроллера — "WHO AvroRecordSetWriter".

Побег от скуки — процессы ETL - 6
Контроллер RecordSetWriter

Контролер RecordSetWriter уже использует предопределенную схему данных. Схема находится в отдельном объекте — регистре схем ("Schema Registry"). В контроллере есть только ссылка на реестр схем и имя схемы.

Побег от скуки — процессы ETL - 7
Регистр схем

Работать с регистром схем довольно просто. Добавляем новый параметр. Его имя — будет именем схемы. Значение параметр — определение схемы.

В регистре схем создана схема who_outbreak, определение схемы:

{
"type" : "record",
"name" : "who_outbreak",
"fields": [
  {"name": "dt", "type": { "type" : "long", "logicalType": "timestamp-millis"}},
  {"name": "country_code", "type" : ["null", "string"], "default": "-"},
  {"name": "country", "type" : ["null", "string"], "default": ""},
  {"name": "region_code", "type" : ["null", "string"], "default": ""},
  {"name" : "died", "type" : "int", "default": 0},
  {"name" : "died_delta", "type" : "int", "default": 0},
  {"name" : "infected", "type" : "int", "default": 0},
  {"name" : "infected_delta", "type" : "int", "default": 0}
 ]
}

Имена и типы атрибутов схемы соответствуют именам и типам полей записи, сформированной sql-запросом.

После выполнения контроллером sql-запроса и передачи данных на выход контроллера в формате схемы данных, структурированный поток передается в контроллер "Delete all records". Это контроллер типа "PutSQL", который может передавать на выполнение sql-команды.

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

Побег от скуки — процессы ETL - 8
delete from who_outbreak;

В параметрах контроллера указываем SQL Statement “delete from who_outbreak;” и ссылку на пул соединений "JDBC Connection Pool". Параметры JDBC стандартные. Пул содержит настройки подключения к конкретной БД, поэтому его можно использовать во всех контроллерах, которые будут работать с этой БД.

Данные или атрибуты FlowFile не обрабатываются в процессоре, поэтому вход и выход процессора идентичен.

Последний процессор "PutDatabaseRecord".

Побег от скуки — процессы ETL - 9
Запись в БД

В этом процессоре указываем Reader, в котором используется определенная схема who_outbreak. Так как мы удалили все записи в предыдущем процессоре, используем простой INSERT для добавления записей в таблицу. Указываем пул соединений DBCPConnectionPool, далее указываем БД и имя таблицы. Имена полей в схеме данных и БД совпадают, то больше никакой дополнительной настройки проводить не нужно.

Все процессоры, контроллеры и регистры схем нужно перевести в состояние Running (Start).
Процесс доставки данных готов. Если положить файл WHO-COVID-19-global-data.csv в каталог D:input, то в течении 20 секунд он будет удален, а данные пройдя через процесс доставки данных будут сохранены в БД.

Сейчас вынашиваю планы на вторую часть статьи, в которой планирую описать второй процесс ETL — чтение данных с сайта, обогащение, загрузка в БД, в файлы, расщепление потока на несколько потоков и сохранение в файлы. Если тема интересна — пишите в комментарии, это мне прибавит стимула быстрее написать.

На рисунке изображение в интерфейсе Apache NiFi описанного процесса (справа) и, для затравки, процесса для второй статьи (слева).

Побег от скуки — процессы ETL - 10

Автор: alian

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js