Как мы переписали архитектуру Яндекс.Погоды и сделали глобальный прогноз на картах
Как говорится, по традиции раз в год мы в Яндекс.Погоде выкатываем что-нибудь новенькое. Сначала это был Метеум – традиционный прогноз погоды с помощью машинного обучения, затем наукастинг – краткосрочный прогноз осадков на основе метеорологических радаров и нейронных сетей. В этом посте я расскажу вам о том, как мы сделали глобальный прогноз погоды и построили на его основе красивые погодные карты.
Сперва пару слов про продукт. Погодные карты — способ узнавать погоду, очень популярный на западе и пока что не очень популярный в России. Причиной тому является, собственно, сама погода. Из-за особенностей климата наиболее населенные регионы нашей страны не подвержены внезапным погодным катаклизмам (и это хорошо). Поэтому интерес к погоде у жителей этих регионов скорее бытовой. Так, людям в центральной России важно знать, например, какая погода будет в Москве в выходные или что в четверг в Питере будет дождь. Такую информацию проще всего узнать из таблицы, в которой будет дата, время и набор погодных параметров.
С другой стороны, жителям Восточного Побережья США важнее знать траекторию очередного урагана с красивым женским именем, а фермерам из Дакоты — следить за распространением града по полям, на которых растет кукуруза. Такую информацию гораздо проще узнавать из карты, чем из множества таблиц. Так и получилось, что погодные сервисы в России — это скорее таблицы, а на Западе — скорее карты. Однако, и в России существуют паттерны потребления погоды, когда пользователю нужно знать где именно будет погода, которая ему нужна: это люди, выбирающие место для пикника в выходные, спортсмены, особенно с приставкой «винд» и «кайт» и, наконец, дачники. Именно для этих категорий пользователей мы и сделали свой продукт. А теперь я расскажу о том, что у него под капотом.
Расчет прогноза: персональный и глобальный
Мы сразу решили, что для построения карт наш прогноз должен стать глобальным. Ну хотя бы потому, что карты, покрывающие не весь земной шар, отдают средневековьем. Таким образом, нам нужно было расширить Метеум до глобального покрытия. Однако, предыдущая архитектура системы плохо поддавалась горизонтальному масштабированию.
Краткое содержание предыдущих серий. Как помнит внимательный читатель, в первой реализации Метеума мы рассчитывали прогноз погоды по мере необходимости. Как только пользователь попадал к нам на сайт, мы собирали для его координат список факторов и передавали в обученную модель Матрикснета. За сбор факторов отвечал микросервис, который мы называли vector-api. Микросервис был хорош всем, кроме одного: при добавлении новых факторов и/или при расширении географии покрытия, мы приближались к лимиту памяти физических машин, на которых микросервис работал. Кроме того, само по себе формирование ответа итогового погодного API содержало дорогую по времени и по нагрузке на процессор операцию с применением модели Матрикснета. Оба фактора сильно препятствовали построению глобального прогноза. Плюс к тому, в нашем бэклоге образовалась целая очередь из факторов, которые увеличивали точность прогноза в экспериментах, но не могли поехать в продакшен в связи с ограничениями, описанными выше.
Также мы столкнулись с недостатками выбранной архитектуры для карты осадков, горячо полюбившейся многим пользователям. Для хранения и обработки данных, необходимых для построения информации об осадках, использовалась СУБД PostgreSQL с расширением PostGIS. Во время летних гроз количество запросов в тяжелые ручки в секунду молниеносно превращалось из сотен в десятки тысяч, что влекло за собой высокое потребление процессоров и сетевых каналов серверов баз данных. Эти обстоятельства послужили дополнительным стимулом задуматься о будущем сервиса и применить иной подход в обработке и хранении погодных данных.
Альтернативой расчету погоды в рантайме был предварительный расчет прогнозов для большого набора координат по мере обновления факторов. Мы остановились на глобальной сетке, покрывающей сушу с разрешением 2×2 км, а воду – с разрешением 10×10 км. Сетка разбита на квадратики 3 на 3 градуса – эти квадратики позволяют нам параллельно готовить факторы для модели и обрабатывать результаты. Вот как это выглядит на карте.
Разбиение областей глобальной регулярной сетки
Процесс подготовки погодных факторов начинается с кластера Meteo. В соответствии с названием, на этом кластере происходит «классическая метеорология». Здесь мы скачиваем данные наблюдений и прогнозы моделей GFS (США), ECMWF (Англия), JMA (Япония), CMC (Канада), EUMETSAT (Франция), Earth Networks (США) и многих других поставщиков. Здесь же происходит расчет погодной модели WRF для наиболее интересных для нас регионов. Метеорологические данные, полученные от партнеров или в результате расчетов, обычно запакованы в форматы GRIB или NetCDF с разными уровнями сжатия. В зависимости от поставщика или способа расчета, эти данные могут покрывать Московскую область или весь мир и весить от 200Мб до 7Гб.
Из метеокластера файлы с прогнозами погоды попадают сначала в MDS (Media Storage, хранилище для больших кусков бинарной информации), а потом в YT — нашу Яндексовую Map-Reduce систему. Работа в YT делится на два этапа, условно названные «подвоз» и «применение». Подвоз — это подготовка факторов для последующего применения обученной модели. Факторы надо корректно переинтерполировать на итоговую сетку, привести к единым единицам измерения и разрезать на квадратики 3 на 3 градуса для параллельной обработки и применения. Сложность процедуры здесь состоит в больших объемах данных и в необходимости проделывать упражнение каждый раз, когда пришли новые данные о прогнозе для какой-либо области.
После того как первая ступень отработала, начинается применение заранее обученных моделей машинного обучения. В первой реализации Метеума мы могли прогнозировать только два основных погодных параметра с помощью машинного обучения: это температура и наличие осадков. Теперь, когда мы перешли на новую схему расчетов, мы можем использовать тот же подход для вычисления остальных параметров погоды. Применение машинного обучения дает ощутимый прирост в точности для новых параметров: давления, скорости ветра и влажности. Ошибка прогнозирования этих параметров на 24 часа вперед падает на величину до 40%. Помимо того, что для многих пользователей важны максимально точные показатели ветра и давления, это улушение позволяет нам более точно рассчитывать еще один популярный параметр — температуру по ощущениям. Он складывается из обычной температуры, скорости ветра и влажности. Еще одним заметным новшеством стал новый способ расчета погодных явлений. Теперь в его основе лежит мультиклассификационная формула — она определяет не только наличие или отсутствие осадков, но также их тип (дождь, снег, град), а еще наличие и балльность облачности.
Сложность при применении обученных моделей состоит в том, что для каждого прогноза надо выполнить порядка 14 миллиардов операций. Факторов, необходимых для каждого расчёта — сотни, и список этих факторов весьма подвижен: мы постоянно с ними экспериментируем, пробуем новые, добавляем целые группы, выкидываем слабые. Далеко не все факторы берутся напрямую от поставщиков. Мы экспериментируем с факторами-функциями нескольких параметров. Правила формирования такого количества фичей очень сложно поддерживать в виде кода на Python: громоздко, трудно делать ленивые вычисления, трудно анализировать и диагностировать, какие фичи (или исходные данные для фичей) уже не нужны. Поэтому мы изобрели свой аналог LISP. Строго говоря, это не LISP, а уже готовое AST, которое очень похоже на диалект LISP, в котором учтена специфика наших данных. Процессор этого LISP-а мы сделали таким, как нам надо: ленивым и кэширующим. Поэтому мы, во-первых, не вычисляем факторы, которые стали уже не нужны, во-вторых, не вычисляем дважды, то, что нужно дважды. Эти механизмы автоматически распространяются на все расчёты. А благодаря тому, что это формальное AST мы можем: легко анализировать что нужно, а что не нужно; сериализовать и хранить отдельно части логики, писать бизнес-части в логи, формировать большие части логики автоматически (минуя представление в виде кода), версионировать их для проведения экспериментов и так далее. Оверхед же получился совершенно незначительный, так как все операции выполняются сразу над матрицами.
Общая схема поставки данных в API
Так как требования к потреблению памяти и времени ответа были очень высоки еще с постановки задачи, старый микросервис написанный на Python мы решили переписать на C++. И это дало свои результаты: Время ответа сервиса в 99 квантиле упало со 100мс до 10мс.
Перевод сервиса на новую архитектуру бэкендов позволил нам отдавать прогнозы погоды нашим внутренним и внешним партнерам напрямую, минуя кэши и расчеты моделей в рантайме, дал возможность при срочной необходимости с легкостью масштабировать нагрузки с использованием облачных технологий Яндекса.
Итого, новая архитектура позволила нам уменьшить тайминги ответов, держать бОльшие нагрузки и избавиться от рассинхрона данных, отдаваемых разным партнерам. Данные Яндекс.Погоды представлены в большом количестве сервисов Яндекса и не только: главная страница Яндекса, плагин погоды в Яндекс.Браузере, погода на Рамблере и так далее. В результате все они получили возможность отдавать именно те значения, которые в этот момент видят пользователи основной страницы Яндекс.Погоды.
Ближе к людям: тайл-сервер и фронт
Для того, чтобы отрисовать прогнозы на наших новых картах, необходимо достаточно много дополнительной обработки. Сначала мы запускаем MapReduce операции на тех же данных, что отдаются микросервисом и API для формирования данных на весь мир на регулярной широтно-долготной сетке с разрешением в 0.02 градуса. На выходе для каждого параметра: температуры, давления, скорости и направления ветра, а также для каждого горизонта: времени прогноза в будущее или времени факта в прошлое мы получаем матрицы размером 9001*18000.
После этого мы строим проекцию Меркатора по этим данным и нарезаем их на тайлы в соответствии с требованиями API Яндекс.Карт. Здесь мы встречаемся с одной из самых больших сложностей в цепочке подготовки погодных карт: количеством тайлов, которые нужно оперативно обновлять при генерации каждого нового прогноза. Так каждый следующий уровень приближения карты (zoom) требует в 4 раза большего количества тайлов чем предыдущий. Несложно прикинуть, что для всех уровней приближения от 0 вплоть до 8 нужно подготовить
картинок. Всего для каждого из четырёх параметров мы показываем 25 горизонтов, из них прогнозов в будущее 12. Поэтому каждый час требуется обновить 1048560 картинок.
По мере готовности картинок мы загружаем их во внутреннее хранилище файлов Яндекса с высокой параллельностью. Как только весь слой карты для всех зумов загрузился, мы подменяем значение индекса, по которому фронт понимает, куда идти за новыми тайлами. Таким образом достигается достаточно высокая скорость появления новых прогнозов на карте и консистентность данных в пределах времени прогноза.
Даже несмотря на серьезную подготовку на бэкенде, отрисовка и анимация погодных карт тоже представляет собой сложную задачу. Рассказать все в одной статье не получится, поэтому здесь мы сделали акцент на самой заметной фиче — отрисовке анимированных частиц, показывающих направление втера.
Так выглядят частицы анимации ветра на картах погоды
Для оптимизации потребления ресурсов клиентского устройства, анимация ветра выполнена с использованием WebGL. WebGL позволяет задействовать значительные ресурсы графического адаптера, разгружая процессорный поток выполнения кода, а также оптимизируя расход аккумулятора. Задачей процессора в этом случае становится установка аргументов выполнения шейдерных программ. Для перемещения частиц используется подход хранения положения частицы в 2х цветовых каналах текстуры для каждой оси (x/y). Таких текстур положения две: одна хранит текущее положение частиц, вторая предназначена для сохранения нового состояния.
Процесс отрисовки частиц описан несколькими WebGL программами. Для большей совместимости использована первая версия этого стандарта. В браузере использование WebGL выполняется через соответствующий контекст элемента canvas. Так как графический ускоритель способен выполнять в несколько раз больше параллельных операций через процессор, то перемещение частиц следует выполнять через программу WebGL. Выходной результат выполнения такой программы представляет набор точек в заданном пространстве. Это пространство по умолчанию является видимой областью своего canvas, то есть, экраном пользователя. Однако есть возможность указать целью отрисовки текстуру, используя фреймбуферы.
При старте слоя ветра процессор генерирует начальное положение частиц, создавая типизированный массив элементов в диапазоне 0. 255, в количестве (частицы * компоненты) (RGBA). Из этого массива создаются две WebGL текстуры положения. Данные по скорости ветра также записываются в текстуру, чтобы видеокарта имела к ним доступ. Красный и зеленый канал этой текстуры содержат значение параллельной и меридиональной скоростей соответственно, причем значение 127 соответствует отсутствию ветра, значения меньше 127 задают скорость ветра в отрицательном направлении по оси, значения больше — в положительном. С помощью подготовленных текстур происходит отрисовка текущего положения частиц. После отрисовки, одна из текстур положения обновляется, используя вторую текстуру как источник данных по текущему состоянию. Следующие видимые кадры будут сформированы при помощи отрисовки предыдущего снимка положения частиц с увеличенной прозрачностью, поверх которого будет нанесено текущее положение частиц. Таким образом получаются частицы с затухающими хвостами.

Так выглядят частицы анимации ветра в процессе их создания
Как оказалось, текущий алгоритм кодирования положения частиц в пикселях текстуры для качественной визуализации требует поддержки на аппаратном уровне высокой точности вычислений и float-значений в самих текстурах, чего зачастую нет на мобильных устройствах, поэтому алгоритм будет переработан и улучшен.
Заключение и планы
Вот примерно все, что я хотел рассказать вам об архитектуре Яндекс.Погоды. За этот год мы полностью переделали сервис изнутри (об этом рассказано выше) и снаружи (практически все платформы, на которых присутствует Я.Погода были существенно перерисованы). Финалом этих изменений стали интерактивные погодные карты, которые вы можете попробовать на нашем сервисе.
Однако, мы никогда не останавливаемся на достигнутом. В следующем году вас ждет много интересных продуктовых и технологических апдейтов: от сервиса, позволяющего исследовать климат в разных уголках Земли до ответа на вопрос «чем дышит человек». И это, разумеется, далеко не все. Оставайтесь с нами.
деревья на ветру
ibatkov спасибо конечно за ссылки, но меня интересовала имитация ветра средствами макса и не для анимации, а для статики. Мне бы хотелось получить эффект порывистого ветра. когда вся листва и ветви наклонены в одну сторону, а не просто колышущуюся листву. Типа вот этого:https://yandex.ru/images/search?img_url=http%3A%2F%2Fwww.photohost.ru%2Ftt%2F96%2F64%2F117397.jpg& ;_=1442167900214&p=4&nomisspell=1&text=%D0%B8%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5%20%D0%B2%D0%B5%D1%82%D1%80%D0%B0&redircnt=1442167880.1&pos=139&rpt=simage
Что, нет знающих людей по теме?
А в Growfx можно с любой своей моделью это проделать?
Товарищи, нужно сделать довольно много анимированных деревьев Grow FX в одной сцене. Пять разных деревьев, когда их только пять одна только превью анимация занимает вагон времени пока все пять деревьев пересчитаются для каждого кадра. А если этих деревьев нужно штук пятьдесят?
С Гроу ФХ работаю впервые. Корона скаттер его не хавает.
Как бы кто поступил?
Добавлена возможность кеширования данных Cache mode. Теперь имеется возможность запекать анимацию роста или ветра всего растения, и затем использовать кеш файлы в других проектах. Процесс записи кеш файлов многопоточный, поэтому это работает быстрее для всей анимации, чем обычный просчет дерева при рендеринге;Добавлена возможность запекания направления линий Bake Direction в модификаторе Object Reaction. Это позволяет привязывать вьющиеся растения к анимированному объекту реакции;Добавлены новые параметры Chaos pos %, Angle Bend и Length % для Path Position Distributor;Добавлена возможность конвертирования Path Distributor в Path Position Distributor;Добавлена опция Shift Start Point для Noise direction modifier;Добавлена опция Scale proportionally для Leaves mesh;Добавлена опция By Width Graph Points для Leaves mesh;Улучшена Stop stitching возможность для Meta mesh, а также добавлена опция Stop stitching for children;Также исправлено множество ошибок.
вот такая последовательность, очень быстро
вот результат уже анимации в прокси. просто изменения длины пути анимация
короче проверил все пашет. Удачи!
только что-то с группами сглаживания произошло, но на форуме вроде было про это
У кого нет еще гроу, стучитесь 😉
вот такая последовательность, очень быстро
вот результат уже анимации в прокси. просто изменения длины пути анимация
короче проверил все пашет. Удачи!
только что-то с группами сглаживания произошло, но на форуме вроде было про это
У кого нет еще гроу, стучитесь 😉
Товарищи, провёл некоторые тесты, вот результат:
Point Cash (.pc2) vs Grow FX Cash
Максовский поинт-кэш весит намного меньше, чем Гроу ФХ кэш. Для дерева примерно 600к поликов Гроу ФХ кэш на 50 кадров у меня весил примерно 1,5 Гб, а Максовский поинт-кэш (.pc2) весил 500 Мб для 100 кадров. Кэш для в два раза большего кол-ва кадров весил в три раза меньше.
Резюме – анимировать через афтер меш модифайр, запекать кэш, конвертировать в эдитэйбл поли.
Editable Poly (Scene Object) vs Corona Proxy
После конвертации в Эдитэйбл поли с поинт кэшем, дерево становится объектом который можно спокойно скармливать Корона Скаттеру и/или переводить в Корона Прокси. Поинт кэш моего тестового дерева на 600к поликов весил 500 Мб на 100 кадров анимации. Корона прокси на эти же 100 кадров анимации весил почти 1 Гб, т.е. почти в два раза больше. Но, соответственно, разгружал вью-порт. 500Мб или 1 Гб большой разницы нет, но если у вас деревья не 600к, а 2-3кк поликов, и этих деревьев энное кол-во разных, то вес файлов будет намного больше и разница в два раза между объектом сцены с поинт кэшем и прокси заставит задуматься.
Анимация.
В случае анимации для лёгкого ветерка, качающего только листья и малые ветки, не вижу необходимости просчитывать в кэш все сотни/тысячи кадров, достаточно просчитать кадров сто и зациклить пинг-понгом, чтоб была бесшовной. Это можно как через кёрв-эдитор (зациклить поинт кэш если оставлять дерево как объект сцены), так и в настройках Корона Прокси (пинг-понг режим анимации есть и там).
И в случае луповой анимации на мой взгляд конвертация безальтернативна. Я отмечал везде галочки «Луп анимэйшн» в Винд Модифайре Гроу ФХ, ставил корректное кол-во кадров, но всё-равно первый и последний кадр лупа точно не совпадали и при переходе к следующему лупу анимации было заметно лёгкое подёргивание картинки. Так что только зацикливание Пинг-Понгом.
Сразу говорю, что не претендую на то, что нашёл самый лучший и оптимальный способ – просто провёл ряд тестов и делюсь инфой. Я свои выводы сделал, вы свои делайте сами.







