Иллюстрации Лизы Сюй

Команда Clover по обработке и анализу данных занимается созданием моделей машинного обучения (МО), предназначенных для улучшения выявления и лечения хронических заболеваний. Одна из вещей, которая делает нашу платформу уникальной, — это петля обратной связи, которая обеспечивает быструю итерацию и повышенную точность модели. В 2022 году конвейер, обрабатывающий наши данные для машинного обучения, будет работать почти неделю. Неделя для ИТ в здравоохранении — это молниеносно, но нас как технологическую компанию это не устраивало.

Присоединяйтесь к нам, и мы поделимся некоторыми оптимизациями, которые сделали наши инженеры по машинному обучению, чтобы сократить продолжительность нашего типичного конвейера с более чем недели до менее чем одного дня!

Становитесь быстрее, будучи ленивее

В нашем конвейере используются очень распространенные инструменты: код Python, работающий в Airflow и поддерживаемый базой данных PostgreSQL. Мы следовали простому трехэтапному процессу, чтобы оптимизировать его:

  1. Поймите, что делал наш код
  2. Определите, что наш код не должен делать
  3. Удалите как можно больше ненужной работы

Первый шаг был, наверное, самым важным. Мы активно использовали инструменты профилирования, чтобы понять, что делает наш код. Поскольку мы используем Google Cloud Platform, мы использовали Cloud Profiler, чтобы прозрачно измерить, на что наши рабочие места тратили больше всего времени.

Выявление ненужной работы требовало глубокого понимания нашего кода. Мы задавали себе такие вопросы, как «Требуется ли эта база данных туда и обратно?» или «Является ли эта функция анализа даты более гибкой, чем нам нужно?»

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

Имея в виду эту структуру, вот некоторые вещи, которые мы нашли и улучшили.

Время замедлилось до ползания

Мы обнаружили кое-что удивительное при изучении профилей для одного из наших более длинных заданий: примерно 20% его 10-часового времени выполнения тратилось на синтаксический анализ временных меток из строк. Это казалось чрезмерным, поскольку все строки хранились в стандартном формате ISO 8601.

После расследования мы переключились с использования очень гибкой, но медленной функции to_datetime() в pandas на использование гораздо более жесткого и быстрого метода datetime.fromisoformat() из стандартной библиотеки. После этого наш код для анализа временных меток перестал работать в замедленном режиме.

Протобуфа более чем достаточно

Clover интенсивно использует Protocol Buffers и gRPC для связи между службами, и наш конвейер машинного обучения даже получает необработанные данные в виде сообщений protobuf. К данным этих сообщений можно получить доступ непосредственно в коде Python, но вы также можете преобразовать их в стандартные словари Python для более знакомой семантики.

Мы нашли 4-х часовую работу, которая тратила 3 ​​часа на то, чтобы покорно преобразовывать сообщения protobuf в словари. Мы поняли, что можем извлекать те же данные из сообщений, не преобразовывая их, и тем самым избавились от 75% обязанностей этой работы.

Случай с отсутствующим индексом

Это была старая как мир история: запрос к базе данных, выполняющий поиск в столбце идентификаторов, занимал большую часть времени выполнения 11-часового задания. Но был уникальный индекс, который включал столбец ID! Что дает?

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

Переход к SQL

Последним шагом в нашей работе по созданию агрегированных функций для наших моделей машинного обучения было вычисление массивного поворота с использованием метко названного метода DataFrame.pivot(). Вычисления не только потребляли тонну памяти и требовали от нас запускать их в пакетном режиме, но это была единственная часть работы, которая требовала извлечения всех данных из базы данных. Это привело нас к вопросу: можем ли мы вычислить точку опоры в SQL?

После одного очень уродливого набора операторов CASE мы смогли полностью запустить огромный свод в нашей базе данных, резко сократить использование памяти, устранить необходимость в пакетной обработке и сократить 20-часовую работу до 5 часов.

Точно так же отдельный процесс, генерирующий наши аналитические наборы данных для обучения и вывода модели ML, запрашивал каждую агрегированную функцию из базы данных отдельно и объединял их в памяти как pandas DataFrames. Мы переписали это, чтобы сделать как можно больше соединений в базе данных, и после добавления соответствующих индексов добились еще более впечатляющего ускорения с 20 часов до 2 часов.

Не очень постепенные улучшения

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

К счастью, наши необработанные данные и API, которые мы использовали для доступа к ним, уже содержали ключевой компонент для инкрементного конвейера: поле, представляющее время последнего изменения записи. Мы решили модернизировать весь наш конвейер с помощью режима инкрементной обработки на основе этой временной метки.

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

К счастью, награда в конце полностью окупила наш квест по инкрементной обработке. Тяжеловесные задания загрузки, которые выполнялись более 60 часов и растягивались каждую неделю, сокращались до 5 часов или даже меньше, если новые данные не были доступны. 15-часовые последующие задачи также сократились до 5 часов или меньше в зависимости от объема новых данных. После полной реализации инкрементальной обработки мы наконец-то увидели, что наше обычное время выполнения упало ниже 24-часовой отметки.

Обучение и что дальше

Мы извлекли несколько уроков из нашего приключения по оптимизации:

  • Синтаксический анализ временных меток может быть очень медленным, но он не должен быть медленным, если форматы ваших временных меток одинаковы.
  • Преобразование сообщений protobuf в памяти может быть медленным, и его следует избегать без необходимости.
  • Индексы базы данных важны для хорошей производительности в рабочих нагрузках с большим количеством операций чтения, но они должны быть указаны правильно.
  • Перенос вычислений в базу данных может значительно повысить производительность, особенно когда это избавляет вас от необходимости извлекать данные из базы данных.
  • Прежде всего, старайтесь не обрабатывать повторно данные, которые не изменились!

Наши специалисты по обработке и анализу данных очень довольны новой средой выполнения, рассчитанной на несколько дней, но наши инженеры по машинному обучению подозревают, что они могли бы быть еще счастливее. Мы постоянно добавляем данные и функциональность в конвейер, и мы ожидаем, что в какой-то момент в ближайшем будущем будут достигнуты ограничения Python и PostgreSQL. В настоящее время мы изучаем способы перепроектирования конвейера для использования более масштабируемых вычислительных механизмов и механизмов хранения, таких как Apache Spark и BigQuery.