AWS, история третья. Разрывы
Все статьи из цикла AWS
- Амазон
- AWS, история первая. Внезапный мегабайт
- AWS, история вторая. Афина прекрасная
- AWS, история третья. Разрывы
- AWS, история четвертая. Когда null не совсем null
В третьей истории расскажу, как я страдал от сетевых проблем в Амазоне. Как абонент Уральский: раньше не было разрывов, а теперь есть разрывы.
У нас в проекте много лямбд, и каждая вызывает другие. При таком раскладе невыгодно передавать результат напрямую. Можно использовать S3 как шину данных. Например, одна лямбда вызывает другую и говорит: положи результат в такой-то бакет и файл. Затем опрашивает файл, пока он не появится.
Это удобно, когда результат огромен: иные лямбды производят CSV-шки по нескольку гигабайт. Не гонять же их напрямую между лямбдами. Но файлы привносят хаос: не всегда очевидно, кто произвел файл и кто его потребители. Если изменить путь, через день придет коллега из другого сервиса и скажет: мы каждый день забираем файл из этой папки, куда он делся? Или наоборот: ты обнаружил, что некий сервис производит удобный файл, в котором все есть. Через месяц он перестал появляться, потому что они переехали на что-то другое. Мы вам ничего не обещали.
Файлы CSV удобно стримить прямо из S3. Послал GET-запрос, получил InputStream. Передал его в парсер и готово: получается ленивая коллекция кортежей. Навесил на нее map/filter, все обработалось как нужно, красота. Не нужно сохранять файл на диск.
То же самое с форматом JSONL, где каждая строка — отдельный объект JSON. Из стрима получаешь Reader, из него ленивый line-seq, и пошел колбасить.
Неожиданно это схема перестала работать с большими файлами. Где-то на середине цикл обрывается с IOException. Чего я только не пробовал: оборачивал стрим в BufferedInputStream с разным размером, засекал время обработки, передавал опции в к S3… ничего не помогло. Запросы в Гугл тоже ничего внятного выдали.
У меня подозрение, что дело в неравномерном чтении. Когда вы читаете файл, Амазон определяет скорость забора байтиков. Поскольку мы читаем и обрабатываем стрим в одном потоке, чтение чередуется с простоем на обработку данных. В больших файлах я нашел записи, которые больше других во много раз. Возможно, что когда обработка доходила до них, ожидание было больше среднего, и Амазон закрывал соединение.
Я пытался повторить это на публичных файлах S3: читал в цикле N байтов и где-то на середине ставил длинный Thread/sleep. Но S3 покорно ждал аж две минуты, и эксперимент провалился.
Словом, я так и не выяснил, почему вылазит IOException, но смог это исправить в два шага.
Первый — файл S3 тупо записывается во временный файл, и дальше с ним работаешь
локально. Перенос стрима делается одним методом InputStream.transferTo
. Во
время переноса ошибки связи не возникают.
Второй — иные агрегаты достигают 3.5 гигабайтов, а у лямбды ограничение на 10 гигабайтов места. Скачал три агрегата — и привет, out of disk space. Поэтому при записи файл кодируется Gzip-ом, а при чтении — декодируется обратно.
Все вместе дало мне функцию, которая:
- посылает запрос к S3, получает стрим
- создает временный файл
- переносит стрим в файл, попутно сжимая Gzip-ом
- возвращает
GzipInputStream
из файла
Со стороны кажется, что вызвал get-s3-reader
— и байтики потекли, делов-то. А
внутри вот какие штучки.
Примечательно, что в одном проекте я сам спровоцировал
IOException
. Разработчик до меня позаботился о переносе файла S3 во временный
файл. Я подумал, что он просто не знает, как обработать данные из сети и убрал
запись на диск. Возможно, он что-то знал, но не оставил комментария. А надо
было!
Из этой истории следует: никогда не обрабатывайте файл S3 сразу из сети.
Сохраните его во временный файл и потом читайте локально — так надежней. Рано
или поздно вы схватите IOException
, а на расследование будет времени. Чтобы
сэкономить диск, оберните файл в Gzip.
На этом я закончу истории про Амазон. У меня есть еще несколько, но они не такие интересные и сводятся к тезису “думал так, а оно эдак”. Ну и обсасывать одну и ту же тему уже не хочется.
Думаю, вы поняли, что в Амазоне хватает странностей. Это не баги, потому что все
они описаны в документации. Примерно как в Javascript: никто не знает, почему
{} + [] = 0
, а [] + {} = [object Object]
. Да, в стандарте описано, но кому
от этого легче? Выбирая Амазон, закладывайте время и деньги на подобные
непонятки.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter