Как кодировка файла сломала EMR Spark

История о том, как изменение кодировки файла сломало Spark в EMR и весь пайплайн.

Начну с предыстории :)

У нас имеется один клиент у которого есть огромные пайплайны, которые включают в себя:

  • API Gateway
  • Тонны Lambdas
  • StepFunctions
  • EMR’ы со спарком для преобразования сотни гигабайт данных в parquet файлы
  • SSM Run Commands для создания Redshift таблиц для чтения perquet файлов.

Все это приправлено десятками SQS, Firehouse, ElasticSearch.

Как работает пайплайн

Каждый понедельник тригерится лямбда, которая запускает EC2 для скачивания очень ОЧЕНЬ много данных на S3 бакет.

Как только в бакете появляются файлы - тригерится EMR для преобразования файлов parquet файлы.

Ну а дальше они обрабатываются другими EMR’ами и сервисами, которые выполняют не менее важную работу, что бы потом мы могли зайти на сайт и увидить уже преобразованные данные которые вытягиваются из Redshift через API Gateway с лямбдами.

Что сломалось

В один прекрасный день, один файл (размером в 450Mb), который обрабатывал Spark изменил свою кодировку из us-ascii на utf16le .

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

Мало того, с такой кодировкой у вас перестают нормально работать команды sed и gsub

Сейчас покажу на примере.

Для начала давайте убедимся, что за кодировка файла, для этого выполняем команду file -i NAME

Check encoding

Данный файл имеет следующий вид:

A'~'1111111'~'1111111'~'2021-02-17'~''~'Secret name 1'~'#@#@#A'~'2222222'~'2222222'~'2021-02-18'~''~'Secret name 2'~'#@#@#A'~'33333333'~'33333333'~'2021-02-19'~''~'Secret name 3'~'#@#@#

Где '~' - делимитер а #@#@# - терминатор


И так, начнем.

EC2 скачивает данный файл и помещает его на S3, при этом делает замену делиметра и терминатора на | и \n соответственно. И для этого используется команда (Этот код писался еще до нас)

count_lt=$(head -c 10000000 "${FOLDER}"/"${file}" | awk "{print gsub(/'~'/, \"\")}") 
count=$(echo $count_lt | cut -d " " -f1)         
[[ $count != 0 ]] && line_terminator="#@#@#" || line_terminator="\\\n"  
[[ $count != 0 ]] && field_delimiter="'~'" || field_delimiter="|"

Он получает кусок данных, ищет там '~' и если количество найденных данных > 0 - тогда идет замена.

Давайте запустим эту команду на файле с кодировкой utf16le и us-ascii

UTF16LE:

UTF16LE

US-ASCII:

ASCII

А все, что я сделал - это просто запустил iconv и поменял кодировку.

Кстати, если использовать в коде выше tr -d '\000' | grep -o "'~'" | wc -l , то получим следующее

Working example


На следующем шаге EMR пытаясь конвертнуть файл в parquet вываливался со странными ошибками

Input row doesn't have expected number of values required by the schema

Где количество полей должно было = 6, но он находит то 10, то 3.

Так как файлу присваивались не правильный делимитер и терминатор, Spark начинал искать в файле пайп | вместо '~' , а так как в тексте было много пайпов - он принимал их за делимитер и ругался, что количество полей не совпадает.


В итоге фикс выглядел следующим:

  • EC2 получает файлы и проверяет кодировку.
  • Как только кодировка не us-ascii или utf-8 - запускается команда iconv которая преобразовывает файл.

Самое забавное, что теперь, EMR стал обрабатывать пул файлов не за ~4 часа а за ~2. Что сократило стоимость пайплайна на 30$ только за один прогон EMR’а.


В заключение хочу сказать, что порой обычная кодировка файла может поломать вам весь пайплайн. Так что, если что-то сломалось и вы не понимаете что - сделайте file -i NAME и возможно это сэкономит вам кучу времени.

comments powered by Disqus