В этой главе рассматривается содержание лекции Управление и обработка больших наборов данных нашего курса Машинное обучение в производстве. Остальные главы смотрите в содержании.
Многие системы превышают ресурсы, предоставляемые одной машиной, и может потребоваться масштабирование с учетом меняющихся требований. Большинство идей о том, как проектировать масштабируемые и распределенные системы, не относятся к машинному обучению — архитекторы программного обеспечения, инженеры по производительности и эксперты по распределенным системам накопили большой опыт проектирования масштабируемых программных систем, например, кластеров серверов, которые реагируют на миллионы запросов. в секунду и базы данных, которые хранят петабайты данных на многих машинах. Технологии распределенного хранения и вычислений данных хорошо зарекомендовали себя и доступны в виде товаров, будь то проекты с открытым исходным кодом или облачные сервисы, и они обычно используются в качестве строительных блоков систем с поддержкой ML.
Для многих систем с поддержкой ML масштабируемость будет важной задачей проектирования, которую необходимо учитывать, будь то (1) сбор, хранение и преобразование больших объемов обучающих данных, (2) сбор и хранение больших объемов данных телеметрии, ( 3) для обработки большого количества запросов на вывод модели или (4) для выполнения больших распределенных заданий по обучению моделей. Следовательно, инженеры-программисты и специалисты по данным, участвующие в разработке систем с поддержкой машинного обучения, получают выгоду от понимания того, как проектировать масштабируемые системы.
Ключевые принципы проектирования масштабируемых систем достаточно хорошо понятны. При создании программных систем разработчики почти всегда полагаются на существующие абстракции и инфраструктуру, а не на собственные. Например, они могут хранить петабайты необработанных данных в Azure Data Lake, выполнять часовые пакетные задания с использованием Apache Sparks на сотнях серверов, размещенных в облачной инфраструктуре Databricks, или устанавливать Apache Kafka на несколько -домашний сервер для масштабируемой потоковой обработки. Однако понимание ключевых концепций и их компромиссов поможет выбрать правильные технологии и спроектировать системную архитектуру таким образом, чтобы ее впоследствии было легче масштабировать.
В этой главе мы даем обзор ключевых идей, оставляя подробности для других книг. Это соответствует нашей цели обучения Т-образных членов команды (см. Введение), которые знают ключевые концепции и проблемы своих коллег, в дополнение к своей собственной специальности, чтобы они могли задавать правильные вопросы, эффективно общаться и знать, когда нужно. привлечь дополнительную помощь в начале процесса.
Пример запуска: хостинг и поиск фотографий в масштабе Google
Давайте снова рассмотрим поиск изображений, но на этот раз в экстремальных масштабах фотосервиса Google, где пользователи могут загружать и упорядочивать свои фотографии на веб-странице и в мобильном приложении. Google не публикует подробную статистику, но Google сообщил в 2020 году, что они хранят более 4 триллионов фотографий от более чем миллиарда пользователей и получают 28 миллиардов новых фотографий в неделю (это около 46 тысяч фотографий, загружаемых в секунду).
Google Фото показывает загруженные фотографии в Интернете и в мобильном приложении, как правило, в хронологическом порядке. Он предоставляет различные способы редактирования фотографий, например, применяя фильтры, часто основанные на машинном обучении. Google Фото предоставляет гораздо больше функций на основе машинного обучения, таких как запуск обнаружения объектов для связывания изображений с ключевыми словами для поиска, обнаружение изображений, которые можно очистить, предложение способов группировки изображений, определение друзей на изображениях и прогнозирование того, какие изображения, вероятно, представляют собой счастливые люди. воспоминания.
Задняя сторона математики показывает необходимый масштаб операции. При консервативном предположении, что для каждой фотографии требуется 3 мегабайта памяти и 2020 количество загрузок, нам нужно обрабатывать 135 гигабайт изображений в секунду. Типичный жесткий диск может записывать 100 мегабайт в секунду, то есть просто для хранения изображений потребуется параллельная запись как минимум на 1400 дисков. Для обнаружения объектов с помощью глубокой нейронной сети нам нужно изменить размер каждого изображения и преобразовать его в вектор признаков, а затем выполнить ряд матричных вычислений во время вывода модели и записать результат обратно в какое-либо хранилище данных. Модели быстрого обнаружения объектов (скажем, YOLOv3) имеют задержку вывода от 20 до 50 миллисекунд на изображение в последних тестах, поэтому нам потребуется не менее 2780 параллельных процессов, чтобы поддерживать прогнозирование для новых входящих изображений. Если бы мы когда-нибудь решили запустить обновленную модель на всех фотографиях, одному последовательному процессу потребовалось бы 2536 лет только для вывода модели на существующих фотографиях.
Некоторые характеристики сервиса будут важны для дальнейшего обсуждения дизайна. Количество загруженных изображений, скорее всего, зависит от времени суток и сезона и значительно различается между пользователями. Кратковременные отключения и задержки, скорее всего, не критичны для бизнеса или безопасности, но плохо отражаются на продукте. При загрузке фотографий пользователи разумно ожидают, что загруженные фотографии сразу же появятся в их библиотеке, отсортированные по дате. Пользователь, вероятно, также ожидает, что отображаемое общее количество фотографий будет обновлено сразу после загрузки. Напротив, пользователи, скорее всего, не заметят, если поиск новых изображений не будет работать немедленно. Для предложения фильтров и пометки друзей разные решения пользовательского интерфейса требуют разной скорости отклика в фоновом режиме: если пользователи должны принять предложения фильтров или подтвердить друзей в диалоговом окне загрузки, предложения должны быть готовы немедленно; в качестве альтернативы предложения фильтров могут отображаться в приложении позже, друзей можно помечать в фоновом режиме без участия пользователя, или приложение может выдавать уведомление, когда позже предлагает фильтры. Напоминание пользователям о вероятных счастливых воспоминаниях — это то, что в любом случае запланировано на более поздние времена.
Масштабирование путем распределения работы
Когда мы сталкиваемся с ситуациями, когда мощности конкретной машины уже недостаточно для удовлетворения вычислительных потребностей для конкретной задачи, такой как служба размещения фотографий в нашем примере, у нас обычно есть три варианта: (1) использовать более эффективные алгоритмы, (2 ) использовать более быстрые машины или (3) использовать больше машин. Поначалу первые два варианта могут быть перспективными. Поиск узких мест и оптимизация реализации могут существенно ускорить вычисления, особенно если первоначальная реализация была очень неэффективной. Покупка более быстрых машин (более быстрые или более ЦП, больше памяти, больше места для хранения) может увеличить производительность без необходимости вообще менять реализацию. Тем не менее, оптимизация производительности зайдет так далеко, и лучшее оборудование быстро станет очень дорогим и упирается в физические ограничения задолго до того, как мы достигнем масштаба Google в нашем рабочем примере. Следовательно, истинная масштабируемость почти всегда достигается за счет распределения памяти и вычислений между несколькими машинами.
В контексте масштабируемых систем с поддержкой ML почти каждая часть системы может быть распределена. В некоторых случаях принятие распределенного дизайна обусловлено необходимым распределением развертывания системы, например мобильных приложений, граничных вычислений или киберфизических систем. Во многих других случаях это обусловлено потребностями в масштабируемости при столкновении с огромными объемами обучающих данных (распределенное хранилище, распределенная обработка данных), с дорогостоящими в вычислительном отношении заданиями по обучению модели, которые могут включать специализированное оборудование (распределенное обучение модели), с применением вывода модели к большим объемам. данных или использование вывода модели при одновременном обслуживании многих пользователей (распределенный вывод модели), а также при сборе и обработке больших объемов данных телеметрии (распределенное хранилище, распределенная обработка данных). Мы часто будем видеть распределенные вычисления, включающие сетевые вызовы между компонентами ML и не-ML. В нашем запущенном примере мы видим все эти причины в одной системе: мобильные приложения, большие объемы обучающих данных, дорогостоящие задания по обучению моделей для обучения обнаружению объектов и других моделей, множество входящих фотографий, на которых мы хотим обнаруживать объекты, и множество пользователей, производящих данные телеметрии о том, как они взаимодействуют с системой, и все они распределены по многим компонентам ML и не ML.
Хотя распределенные системы выгодны и часто неизбежны, они сопряжены со значительными проблемами: распределенные вычисления по своей сути сложны. Он вводит новые режимы отказа, такие как обрыв или задержка сетевых подключений и трудности с поиском консенсуса на нескольких машинах. Например, пользователи нашего фотосервиса были бы удивлены, обнаружив, что некоторые фотографии отсутствуют, когда они иногда открывают приложение, только потому, что запросы обслуживались другим сервером, который оказался отключенным во время загрузки фотографий. Разработчики распределенных систем должны вкладывать значительные средства в предвидение сбоев и разработку стратегий для их обработки, от простых механизмов повторных попыток до избыточности и голосования, транзакций и сложных протоколов распределенного консенсуса, которые могут обеспечить гарантии даже перед лицом маловероятных сетевых сбоев. Тестирование и отладка распределенных систем особенно сложны. Мы не сможем гарантировать, казалось бы, простые свойства, такие как то, что каждый элемент обрабатывается ровно один раз, так как любая часть распределенной системы может выйти из строя в любой момент, до того, как она начнет обрабатывать элемент или до того, как она отправит нам подтверждение того, что она обработал предмет. С положительной стороны, распределенные вычисления выигрывают от возможности купить много копий стандартного оборудования, которое дешевле, чем специализированное высокопроизводительное оборудование. Его легче масштабировать (часто почти линейно), просто добавляя больше оборудования.
К счастью, понимание нескольких важных концепций и компромиссов позволяет проектировать и реализовывать распределенные системы на основе надежных абстракций и строительных блоков. Таким образом, задача инженера состоит в том, чтобы в первую очередь принимать соответствующие компромиссные и проектные решения, а также выбирать, на какой инфраструктуре строиться, без необходимости реализовывать низкоуровневые механизмы повторных попыток или протоколы консенсуса.
Хранение данных в масштабе
Инфраструктура хранения данных имеет долгую историю и в целом хорошо изучена. Прежде чем мы рассмотрим механику распространения, давайте вернемся к ключевым абстракциям.
Основы хранения данных
Тремя наиболее распространенными формами подходов к хранению данных являются реляционные данные, данные документов и неструктурированные данные.
В реляционных моделях данных данные структурированы в виде таблиц, в которых все строки имеют согласованный формат, определенный в схеме. Типичным языком для взаимодействия с реляционными базами данных является SQL, который декларативно определяет запросы, какие данные должны быть получены или изменены, позволяя инфраструктуре базы данных выяснить, как наиболее эффективно ответить на запрос. Чтобы избежать избыточности в хранилище данных, когда несколько элементов совместно используют информацию, такую как несколько фотографий, сделанных одним и тем же пользователем, или несколько пользователей, отслеживающих одни и те же общедоступные фотоальбомы, данные в отношениях «один ко многим» или «многие ко многим» обычно нормализуются. То есть такие отношения разбиваются на несколько таблиц, ссылающихся друг на друга по ключам. При запросе данных такая информация при необходимости объединяется из нескольких таблиц. Механизмы базы данных обычно также поддерживают индексы для эффективного запроса данных.
select p.photo_id, p.path, u.photos_total from photos p, users u where u.user_id=p.user_id and u.account_name = “christian”
Пример трех таблиц в реляционной модели данных и запроса SQL, связывающего некоторые из них с помощью ключей. Данные нормализованы в том смысле, что данные пользователя и камеры сохраняются только один раз, но связаны с несколькими фотографиями. Обратите внимание, что запрос описывает только то, что должно быть возвращено, но не описывает, как эффективно извлекать данные.
В модели данных документа данные хранятся в виде пар ключ-значение в коллекциях, где значения могут быть простыми значениями (например, числами, строками) или сложными значениями, такими как вложенные структуры объектов (обычно используется JSON). В большинстве систем структура не применяется для значений в пределах одной коллекции, что обеспечивает гибкость. Данные в коллекции могут быть эффективно извлечены с помощью ключа, и часто индексы дополнительно позволяют осуществлять эффективный поиск значений на основе некоторых проиндексированных частей документов. Данные в отношениях «один ко многим» или «многие ко многим» обычно не нормализуются в модели данных документа, но часто хранятся избыточно — в противном случае ссылка на ключ в другой коллекции сохраняется как поле в документе. Если необходимы соединения между несколькими коллекциями, разработчику обычно приходится вызывать базу данных несколько раз, чтобы получить объекты из нескольких коллекций, вместо того, чтобы инфраструктура базы данных выполняла соединения. Базы данных, реализующие такие модели документов, обычно называются базами данных NoSQL.
{ "_id": 133422131, "path": "/st/u211/1U6uFl47Fy.jpg", "upload_date": "2021-12-03T09:18:32.124Z", "user": { "account_name": "christian", "account_id": "a/54351" }, "size": "5.7", "camera": { "manufacturer": "Google", "print_name": "Google Pixel 5", "settings": "f/1.8; 1/120; 4.44mm; ISO271" } } db.getCollection('photos').find({ "user.account_name": "christian"})
Пример хранения данных в модели документа с использованием формата JSON. Данные хранятся не в виде таблиц, а в виде объектов с вложенными внутренними объектами. Обратите внимание, как избыточно сохраняются данные о пользователях и камерах для каждой фотографии. Доступ к данным здесь осуществляется путем поиска по внутреннему полю документа в пределах одной коллекции.
Неструктурированные данные просто хранятся в файле на диске без какой-либо принудительной структуры и какого-либо эффективного механизма доступа. Например, файлы журналов просто хранят строки текста без каких-либо идентифицирующих ключей или обязательной структуры. Чтобы найти определенные значения в неструктурированных данных, обычно приходится просматривать все данные и искать записи, соответствующие определенным шаблонам — обычно нет индекса, который позволил бы нам легко находить данные.
02:49:12 127.0.0.1 GET /img13.jpg 200 02:49:35 127.0.0.1 GET /img27.jpg 200 03:52:36 127.0.0.1 GET /main.css 200 04:17:03 127.0.0.1 GET /img13.jpg 200 05:04:54 127.0.0.1 GET /img34.jpg 200 05:38:07 127.0.0.1 GET /img27.jpg 200 05:44:24 127.0.0.1 GET /img13.jpg 200 06:08:19 127.0.0.1 GET /img13.jpg 200
Пример файла журнала веб-сервера с указанием того, какие файлы были доступны, когда и с какого адреса.
Есть много хорошо известных компромиссов между этими моделями хранения. Например:
- Хранение как отношений, так и документов требует определенного планирования и подготовки для организации и структурирования данных, тогда как хранение данных в неструктурированном формате часто просто добавляет данные в файлы с минимальной инфраструктурой. Как реляционные, так и документные базы данных могут организовать внутреннее хранилище данных для обеспечения эффективного поиска; с подходящими индексами можно очень быстро искать конкретные данные, не читая всю базу данных.
- Нормализация в реляционных базах данных помогает избежать избыточности, что уменьшает пространство для хранения и позволяет избежать несоответствий, но сопряжено с дополнительной сложностью соединений во время запроса. В приведенном выше примере информация о пользователе и информация о камере хранятся избыточно, что неэффективно с точки зрения использования пространства и более сложно постоянно обновлять во многих местах, если данные когда-либо изменятся. Реляционные модели данных хорошо подходят для эффективного выражения отношений «многие ко многим», но базы данных документов лучше всего подходят для извлечения документов с вложенными внутренними структурами без необходимости сложных запросов. Если соединения необходимы в базах данных документов, например, для выражения отношений «многие ко многим», они часто плохо поддерживаются, что приводит к более сложному коду приложения, где разработчики реализуют несколько запросов вручную без использования внутренних оптимизаций в ядре базы данных.
- Принудительные схемы реляционных моделей помогают решить некоторые проблемы с качеством данных (см. также главу Качество данных), но могут показаться ограничивающими, если структура данных часто меняется или схема часто меняется. Проверка схемы предлагается в качестве дополнительной функции во многих базах данных документов, но на практике часто не используется; чаще клиентский код проверяет, имеет ли извлеченный документ ожидаемую структуру при использовании данных, извлеченных из запроса. Неструктурированные данные не имеют схемы, которую нужно применять в первую очередь.
- Структуры вложенных объектов в моделях данных документов часто более естественны для сложных объектов, используемых в программах, которые необходимо собирать из сложных запросов в реляционном хранилище данных. Напротив, реляционные модели хорошо сочетаются с табличными данными, часто используемыми в науке о данных.
Кодирование данных
Для хранения и обмена данными данные обычно кодируются. Это помогает уменьшить размер хранилища и сети. Наиболее распространенные типы кодирования данных:
- Обычный текст (csv, файлы журналов): данные хранятся в текстовом формате, удобном для чтения человеком. Это легко читать и писать без дополнительных библиотек, но применение схемы обычно необходимо реализовывать вручную.
- Полуструктурированные, без схемы (JSON, XML, YAML): данные кодируются как документы, содержащие возможно вложенные пары ключ-значение. Ключи обычно представляют собой строки, а значения могут быть текстом, числами, списками или другими документами с парами ключ-значение. JSON, как и в приведенном выше примере базы данных документов, в настоящее время является наиболее распространенным из них, он удобочитаем для человека и может быть легко прочитан и написан большинством языков программирования. Как правило, не существует принудительного применения схемы, которое потребовало бы, чтобы документ содержал определенные ключи, обеспечение или проверка правильной формы документа остается на усмотрение клиентского кода, считывающего документы.
- Кодирование на основе схемы (реляционные базы данных, Apache Avro, Apache Thrift, Protocol Buffers и другие): данные хранятся в компактном двоичном формате для заданной схемы. Цель состоит в том, чтобы быстро читать и записывать данные и минимизировать размер хранилища, а не в удобочитаемости. Поскольку все значения следуют фиксированной схеме, необходимо хранить только значения, но не ключи, что делает хранилище более компактным. Соответствие схеме обеспечивается базой данных или библиотекой, используемой для кодирования или декодирования данных. Библиотеки обычно доступны для всех популярных языков программирования. Обновления схемы необходимо тщательно планировать, но они в той или иной форме поддерживаются всеми реализациями.
В контексте машинного обучения часто много информации извлекается из файлов журналов, хранящихся в виде простого текста. Специалисты по обработке и анализу данных часто хранят свои таблицы данных в файлах CSV с простым текстом на ранних этапах исследования. При обмене данными между компонентами в системе часто происходит обмен полуструктурированными данными, особенно JSON — это распространенный формат ввода и вывода для многих API, включая службы вывода моделей. При работе в масштабе переход на кодирование на основе схемы может существенно сократить сетевой трафик и размер хранилища в производственных системах. Кодирование на основе схемы достигается за счет необходимости изучения и использования сложных библиотек, определения схем и управления их развитием. Сначала это может быть крутой кривой обучения, но обычно окупается в долгосрочной перспективе, поскольку эволюцией схемы в любом случае необходимо управлять в той или иной форме.
Распределенное хранилище: разделение и репликация
Когда хранение и обработка данных должны выйти за пределы возможностей одной машины, в качестве строительных блоков решений для распределенного хранения данных используются две важные концепции: Разделение — это идея разделения данных, чтобы разные часть данных хранится на разных машинах. Репликация — это копирование данных на несколько компьютеров. С помощью секционирования мы распределяем данные между несколькими машинами, а с помощью репликации мы увеличиваем возможности доступа к этим данным (пропускную способность) и защищаемся от потери данных с помощью избыточного хранилища. Читатели, знакомые с технологией RAID для виртуализации жестких дисков, сочтут эти концепции естественным расширением распределенных систем.
Секционирование. Секционирование — это процесс разделения данных таким образом, что разные подмножества данных хранятся на разных компьютерах. Для секционирования табличных данных мы обычно различаем горизонтальное и вертикальное секционирование. Горизонтальное разбиение данных разделяет данные таким образом, что разные строки хранятся в разных таблицах — например, мы можем хранить фотографии в нашем рабочем примере так, чтобы все фотографии пользователя хранились на одном компьютере, а фотографии разных пользователей могли храниться на разных машины. В противоположность этому, и это менее распространено на практике, вертикальное разбиение данных разделяет данные таким образом, что разные столбцы (или поля в документе) хранятся на разных компьютерах — например, мы можем хранить основную информацию о фотографии на одном компьютере, но расширенные метаданные, такие как камера настройки на другом компьютере, так как сведения о камере извлекаются редко. Вертикально разделенные фрагменты на разных машинах связаны уникальным ключом для каждой строки. Горизонтальное секционирование особенно эффективно, если многие запросы затрагивают лишь несколько строк; вертикальное секционирование полезно, если в разных запросах нужны разные столбцы.
Независимо от того, как разделены данные, некоторая часть инфраструктуры хранения данных или системы в целом должна поддерживать соединения со всеми разделами, знать, как искать или вычислять, где хранятся данные, и отправлять запросы или новые данные во все соответствующие разделы. составление ответов. В зависимости от стратегии разделения клиент может иметь возможность напрямую вычислять, на какой из нескольких машин запрашивать определенные данные (например, путем получения идентификатора машины из хэш-значения ключа), или центральная таблица поиска хранит, какая машина содержит какие данные. В некоторых случаях использования ясно, что имеет значение только подмножество разделов, потому что нужны только определенные строки или столбцы, расположенные в определенных разделах, например, при поиске определенной фотографии, но в худшем случае необходимо отправить запрос ко всем разделам. Как правило, клиентская библиотека для инфраструктуры хранения данных будет обрабатывать все это прозрачно, скрывая всю эту сложность от клиентов, которым база данных представляется как единое целое.
Репликация. Благодаря репликации инфраструктура распределенного хранилища будет хранить реплики данных на нескольких компьютерах. Поскольку все машины имеют одни и те же данные, любая машина может ответить на запрос чтения. Таким образом, мы можем легко увеличить пропускную способность для запросов на чтение, реплицируя данные на большее количество компьютеров.
Сложность репликации заключается в том, как выполнять операции записи: когда данные добавляются или изменяются, это изменение должно быть отражено во всех репликах. К сожалению, мы не можем гарантировать, что все реплики будут выполнять обновления в одно и то же время и в одном и том же порядке — если два клиента независимо попытаются изменить одно и то же значение в одно и то же время или почти в одно и то же время, разные реплики могут получить изменения в разные заказы приводят к разным результатам. Хуже того, обновления могут завершаться сбоем на некоторых, но не на всех репликах по какой-либо причине (например, нехватка места, сбой в сети, аппаратный сбой). По всем этим причинам существует риск несогласованности данных между репликами, когда разные реплики содержат разные представления данных и возвращают разные ответы на запросы. В нашем рабочем примере пользователь может заметить, что удаленная фотография время от времени снова появляется в приложении, потому что приложение считывает данные из разных реплик, но операция удаления на некоторых репликах завершилась неудачно.
Существует множество различных подходов к обеспечению согласованности между репликами, все с различными ограничениями и компромиссами, и все они реализованы в различных готовых инфраструктурных решениях. Наиболее распространенными являются:
- Репликация ведущего и ведомого — это распространенная схема, в которой одна реплика называется ведущей, а все остальные действуют как ведомые. Все изменения выполняются на лидере, который имеет официальное согласованное представление данных. Все последователи получают изменения от лидера. Можно принудительно синхронизировать лидеров и ведомых, но чаще изменения распространяются асинхронно, а ведомые могут немного устареть (устареть) в любой момент, но всегда будут иметь внутренне согласованное состояние, скопированное с ведущего. Инфраструктура такого типа обычно имеет дополнительные функции для обработки отказа лидера путем выбора нового лидера. Этот дизайн встроен во многие популярные базы данных, включая MySQL и MongoDB. Он допускает быстрое чтение, но лидер является узким местом для операций записи.
- Репликация с несколькими ведущими узлами существенно увеличивает внутреннюю сложность, но устраняет узкие места при записи в системах с одним ведущим. Операции записи могут быть применены к любому из нескольких лидеров, и эти лидеры координируют изменения друг с другом, обычно устанавливая протокол для разрешения конфликтов записи, например, находя консенсус, чтобы решить, в каком порядке применяются изменения. В результате в конечном счете получается непротиворечивая окончательная версия, хотя и не обязательно точно та, которую клиент ожидал бы с его локальной точки зрения. Такие протоколы для окончательного разрешения конфликтов записи также широко используются в автономных приложениях, которые лишь изредка синхронизируются с удаленными базами данных, а также при совместном редактировании (например, Google Docs). Сложность заключается в определении стратегии разрешения конфликтов, иногда в зависимости от конкретной проблемы.
- Репликация без лидера полностью отказывается от различий между лидерами и последователями. Чтобы гарантировать, что данные не будут потеряны при сбое реплики, клиенты отправляют операции записи на несколько реплик, откуда инфраструктура позаботится о том, чтобы передать их всем оставшимся. Однако для обеспечения согласованности и обнаружения устаревших данных данные версионируются, и операции чтения запрашивают несколько разделов, обнаруживая проблемы во время запроса и устраняя их на лету по мере необходимости. Этот стиль репликации распространен в базах данных документов, таких как Amazon Dynamo, Cassandra и Voldemort. Этот дизайн жертвует некоторой пропускной способностью чтения в пользу более высокой пропускной способности записи и менее сложной реализации.
С точки зрения разработчика системы, выбор правильной стратегии репликации (и инфраструктуры, реализующей эту стратегию) зависит от относительной важности пропускной способности записи и чтения, а также важности немедленной согласованности и допустимой задержки до тех пор, пока все клиенты не увидят последнее обновление.
В контексте машинного обучения несоответствия или слегка устаревшие версии данных часто приемлемы как незначительный дополнительный шум в данных, к которому большинство алгоритмов машинного обучения довольно устойчивы. Например, в нашем рабочем примере отсутствие нескольких последних фотографий в обучающих данных или отсутствие одной самой последней дополнительной метки, скорее всего, не сильно повлияет на точность модели и все равно может быть исправлено в следующем обновлении модели. В целом, с точки зрения проектирования системы, зачастую гораздо проще добиться масштабируемости, спроектировав систему так, чтобы она допускала некоторые несоответствия или слегка устаревшие данные из базы данных, а не заставляла базу данных обеспечивать идеальную согласованность.
Разделение + репликация. Естественно, разделение и репликация часто сочетаются. Разделение часто необходимо просто для обработки объемов данных, которые превышают возможности одной машины. Репликация — это эффективный способ масштабирования пропускной способности операций чтения, а иногда и записи, который повышает отказоустойчивость за счет создания избыточности. Разбивая данные на разделы, а затем реплицируя все разделы, мы можем получить все эти преимущества (но также унаследовать все проблемы согласованности от репликации) за счет необходимости в большем количестве оборудования.
Даже если дополнительная пропускная способность не требуется, добавление репликации в систему с разделами важно для обеспечения отказоустойчивости, поскольку в противном случае каждый отдельный раздел может выйти из строя и вывести из строя всю систему. Чтобы избежать единой точки отказа, каждый раздел обычно должен быть реплицирован как минимум на двух машинах.
Файловая система Google и Распределенная файловая система Hadoop (HDFS) — два популярных решения, основанных на этих принципах. Оба хранят потенциально очень большие BLOB-объекты данных (файлы), идентифицируемые по имени. Файлы разбиваются на куски, так что фрагмент файла можно идентифицировать по имени и смещению. Фрагменты распределяются между несколькими машинами (разделение) и дополнительно реплицируются с центральным сервером, сохраняющим сопоставление того, где хранится каждый фрагмент. Клиент, желающий прочитать часть файла, запрашивает расположение фрагментов с сервера, а затем может получить содержимое файла с любого из серверов, на которых размещены фрагменты. Для клиента вся эта внутренняя сложность скрыта в библиотеке.
Транзакции. Транзакции — это общепринятая концепция базы данных для выполнения несколько операций чтения и записи, как если бы они выполнялись за один шаг, все или ничего. Это позволяет избежать несоответствий из-за нескольких конкурирующих одновременных операций записи или операций записи, основанных на устаревших данных, которые были обновлены между чтением и записью. Традиционная инфраструктура баз данных предлагает поддержку транзакций и надежные гарантии. Поддержка транзакций также может быть обеспечена в инфраструктуре распределенной базы данных, хотя, как правило, со значительной сложностью реализации и накладными расходами во время выполнения.
Хотя поддержка транзакций кажется желательной функцией, разработчики систем должны задаться вопросом, является ли она абсолютно необходимой из-за значительных накладных расходов на производительность. В контексте машинного обучения, где системы должны быть устойчивыми к шуму и неправильным прогнозам, часто могут быть допустимы незначительные несоответствия, что позволяет разработчикам систем выбирать более простые и быстрые технологии, не обеспечивающие гарантии транзакций. Например, если фильтр применяется к фотографии в нашем рабочем примере, но функция обнаружения объектов уже прочитала старую фотографию для параллельного вывода ключевых слов, потенциальная незначительная несогласованность, вероятно, может быть допустима.
Хранилища данных и озера данных
Хранилища данных и озера данных — это две популярные и очень разные стратегии проектирования для организации данных в системе, обе из которых распространены в системах с поддержкой ML. Каждый стиль поддерживается множеством специализированных инструментов и коммерческих облачных предложений.
Хранилище данных. Хранилище данных — и его меньший, но концептуально похожий родственник витрина данных — обычно представляет собой выделенную базу данных, в которой данные хранятся в агрегированном и единообразном формате, оптимизированном для данных. анализ. Хранилище данных обычно отделено от операционных систем, которые обрабатывают и хранят транзакции в реальном времени и обновляются только пакетами, например, один раз в день. Он оптимизирован для доступа для чтения и только для чтения, за исключением регулярных массовых обновлений.
В нашем работающем примере хранилище данных может агрегировать количество загрузок и просмотров фотографий, частоту применения предлагаемых фильтров или типы объектов, которые обычно обнаруживаются, потенциально упорядоченные по региону, возрасту пользователя, полу пользователя и другим характеристикам учетной записи. , и отслеживается с течением времени. Это позволяет аналитикам данных создавать отчеты о том, как система работает в больших масштабах времени, и детализировать, есть ли определенные группы пользователей, которые могут недостаточно обслуживаться. Эти виды анализа выполняются в масштабе и вычисляются через равные промежутки времени. Традиционно хранилища данных используются для понимания бизнес-кейсов и возможностей путем создания отчетов, а не для мониторинга в реальном времени.
В процессе ввода данных в хранилище данные обычно извлекаются из нескольких источников, как правило, из других баз данных, но, возможно, также из файлов журналов или потоков событий. Затем данные часто трансформируются и объединяются в форму, поддающуюся последующему анализу; например, данные о продажах могут быть сгруппированы по дням или магазинам. Результат загружается в базу данных, представляющую хранилище, и индексируется, что упрощает последующие запросы. Многие инструменты с открытым исходным кодом и коммерческие инструменты упрощают все части этого процесса извлечения, преобразования и загрузки (ETL), и они являются ключевой областью инженеров данных в организации.
Обратите внимание, что все эти задачи очень похожи на сбор и подготовку данных в конвейере машинного обучения (см. главу Автоматизация конвейера машинного обучения), и специалисты по данным могут извлечь выгоду из использования надежных инструментов ETL или интеграции инженеров данных в свою команду, особенно при сборе и интеграции данных из множества различных источников данных внутри организации. Инструменты ETL часто специализируются на (постепенном) извлечении данных из разных источников, различных преобразованиях для очистки данных, интеграции и нормализации данных, а также эффективной обработке больших объемов данных; они обеспечивают надежную функциональность для автоматизации, распараллеливания, мониторинга, профилирования и обработки ошибок.
Конечно, также можно подготовить выделенную базу данных в стиле хранилища данных в качестве основного источника данных для специалистов по данным, строящих конвейеры машинного обучения, перекладывая усилия по сбору, очистке и преобразованию данных на команду инженеров данных в организации. Во многих проектах специалисты по данным также будут собирать данные из существующих хранилищ данных в организации в качестве обучающих данных или как часть их разработки функций, например, при построении модели для прогнозирования для фотосервиса, как часто пользователи хотели бы видеть уведомления, напоминающие их прошлых фотографий на основе совокупности прошлых наблюдений.
Озера данных. Озеро данных – это хранилище данных, хранящихся в необработанном и необработанном формате. Основная идея заключается в том, что данные могут оказаться ценными в будущем способами, которые мы не можем предвидеть сегодня, и что любая форма обработки данных может привести к потере деталей, которые могут быть нам полезны позже. Например, если мы храним только самую последнюю версию фотографий, мы, возможно, не сможем впоследствии определить, какие фильтры применялись чаще всего или какие изображения чаще всего удалялись. В некотором смысле озеро данных — это ставка на то, что будущая польза от данных, даже если мы не можем ожидать какой-либо выгоды сегодня, перевесит стоимость хранения данных. При относительно низких затратах на хранение в наши дни даже небольшие будущие выгоды могут оправдать хранение данных. Эта ставка мотивирована многими историями компаний, которые позже обнаружили ценность своих прошлых данных журналов в проектах машинного обучения, например, когда Google обнаружил, что они могут использовать машинное обучение для создания профилей клиентов из прошлых поисковых запросов для создания целевой рекламы. Усилия по извлечению полезной информации из необработанных данных путем обнаружения соответствующих частей и их преобразования будут отложены до тех пор, пока не будет обнаружено использование данных.
В своей простейшей форме озеро данных представляет собой просто заархивированный каталог, в котором постоянно хранятся необработанные данные в формате, предназначенном только для добавления, обычно в основном это файлы журналов и показания датчиков. Однако многие организации сообщают, что считают полезным приложить хотя бы некоторые усилия к хранению метаданных, чтобы впоследствии иметь шанс определить, какие данные у них есть, как они были получены и как извлечь из них информацию. Доступна выделенная инфраструктура для отслеживания данных и метаданных в озерах данных и облегчения обнаружения, например, для этого можно использовать DataHub.
Когда информация позже извлекается из необработанных данных в озерах данных, выполняются многие из тех же шагов преобразования, что и в конвейерах машинного обучения и инструментах ETL, как правило, в массовых заданиях пакетной обработки (см. ниже) над огромным объемом данных, иногда даже извлекая данные в реальном времени. по мере добавления новых данных в режиме потоковой обработки (см. ниже). Некоторые облачные предложения для озер данных предоставляют выделенную инфраструктуру для обработки хранимых данных в масштабе, которую можно подключить к конвейерам машинного обучения или хранилищам данных.
Конечно, не каждое озеро данных оказывается успешным, и за короткий промежуток времени легко накопить огромное количество бесполезных, недокументированных и невозможных для навигации данных. Поэтому озера данных часто уничижительно называют болотами данных или кладбищами данных.
Распределенная обработка данных
Точно так же, как многим системам потребуется найти масштабируемые решения для хранения данных, им потребуются масштабируемые решения для вычислений, включая вычисления для обслуживания большого количества пользовательских запросов и вычислений, обрабатывающих большие объемы данных.
Мы обсудим четыре различные стратегии: (1) Сервисы и микросервисы, когда на запросы клиентов отвечают сразу же по мере их поступления, оптимизированные для быстрого ответа. (2) Пакетная обработка для выполнения вычислений над очень большими объемами данных, которые обычно занимают от нескольких минут до нескольких дней и оптимизированы для пропускной способности. (3) Потоковая обработка, которая обрабатывает входные события из очереди почти в реальном времени, также оптимизированная для пропускной способности. (4) Лямбда-архитектура, которая объединяет все три для определенных форм обработки.
(Микро-)Услуги
Как объяснялось в главе Думай как архитектор программного обеспечения, сервис-ориентированные архитектуры (включая микросервисные архитектуры) — это распространенная форма системных структур, которые разбивают систему на связанные модули (каждый со своим собственным хранилищем данных), каждый из которых имеет определенную функциональность и каждый могут быть разработаны и развернуты в значительной степени независимо друг от друга. Каждая служба отвечает на запросы, по одному запросу за раз. Системы, использующие сервисы, часто используют удаленные вызовы процедур, отправляя аргументы сервису по сети и получая ответ, часто в формате JSON или сжатый в кодировке на основе схемы (см. выше). В современных распределенных системах службы адресуются по сетевым адресам (URI) и предлагают REST API. Масштабируемость достигается за счет запуска нескольких экземпляров службы, распределенных балансировщиком нагрузки, часто управляемых облачной инфраструктурой. Каждую услугу можно масштабировать независимо.
Службы, как правило, оптимизированы для быстрого отклика, поскольку другие компоненты, вызывающие службу, ожидают немедленного ответа. Когда служба становится перегруженной, она имеет тенденцию отбрасывать запросы или отвечать с большими задержками. Клиенты должны обрабатывать случаи ошибок, которые могут возникнуть при удаленных вызовах процедур, обычно с помощью библиотек удаленных вызовов процедур, которые имеют такие функции, как повторные запросы, если они не получают своевременного ответа.
Модульная природа общих компонентов машинного обучения делает их естественными для сервис-ориентированных архитектур. В частности, компоненты вывода модели — это естественные сервисы, которые получают данные вывода в запросах и возвращают прогнозы (см. главу Развертывание модели). Во многих случаях вывод голой модели развертывается как служба (с кодированием функций или без нее), используемая затем другими службами, которые предоставляют некоторые дополнительные функции, специфичные для приложения, с бизнес-логикой.
Конвейер машинного обучения также может быть развернут как единый сервис, который может вызываться другими частями системы для запуска нового выполнения, обучающего новую модель. Сам конвейерный сервис может запрашивать данные у других сервисов, таких как сервис, предоставляющий обучающие данные.
Балансировка нагрузки и маршрутизация запросов. Сервисно-ориентированные архитектуры способствуют масштабированию систем за счет запуска нескольких экземпляров отдельных служб. Если служба управляет внутренним состоянием, например профилями пользователей, это состояние должно быть общим для всех экземпляров, обычно в базе данных, к которой обращаются все экземпляры службы. Тот факт, что функции логического вывода обычно не имеют состояния (т. е. им не нужно хранить данные между запросами), позволяет легко запускать множество их экземпляров без дополнительных затрат на координацию — иногда это называется Шаблон функции обслуживания без сохранения состояния или Модель как шаблон службы.
При наличии нескольких экземпляров запросы могут прозрачно направляться в разные экземпляры службы с помощью балансировщика нагрузки. Обычно маршрутизация запросов может происходить на сетевом уровне, полностью прозрачная для клиентов, вызывающих службу. Маршрутизация часто связана с логикой управления, которая запускает или останавливает дополнительные экземпляры по мере необходимости при изменении требований. Инфраструктура также может динамически перераспределять службы на оборудование для оптимизации времени отклика и стоимости при наличии ограниченных ресурсов, например, для переноса задач обучения модели на оборудование с поддержкой графического процессора, а затем перераспределения этих машин для вывода модели по завершении. В целом, большая часть сложностей, связанных с масштабированием службы, переносится на сетевую и облачную инфраструктуру и обрабатывается группой эксплуатации.
Механизмы динамической маршрутизации запросов к разным экземплярам сервиса используются не только для балансировки нагрузки, но и для маршрутизации запросов к разным версиям сервиса для экспериментов в продакшене, например, с помощью A/B-тестирования или канареечных релизов (см. главу Качество Гарантия в производстве).
Шлюз API. Шлюз API – это обычное дизайнерское решение для организации доступа к (микро)сервисам и маршрутизации запросов. Обычно компоненты в системе вызывают несколько служб и нуждаются в способе доступа к ним. Кроме того, разные службы могут по-разному кодировать данные для транспортировки. Вместо того, чтобы хранить адрес и реализовывать определенные транспортные характеристики каждой службы в каждом компоненте, использующем службу, шлюз API предоставляет единую точку доступа ко всем службам в системе с помощью унифицированного интерфейса удаленного вызова процедур.
Шлюз API действует как единая точка доступа для всех служб в системе. Если служба разделена на несколько служб, если служба изменена для использования другого протокола для связи или если служба перемещена на новый адрес, это полностью скрыто от клиентов. Шлюз API может действовать как единственное место для управления аутентификацией и авторизацией; это хорошее место для реализации ограничения скорости, обеспечения механизмов повторных попыток и восстановления, если службы недоступны, а также для сбора данных телеметрии. Шлюз API можно комбинировать с балансировщиком нагрузки (см. ниже) и механизмами кэширования. Шлюзы API также часто предлагают агрегацию запросов для объединения нескольких запросов к разным службам в одном вызове между клиентом и шлюзом API, чтобы уменьшить количество сообщений, передаваемых по сети туда и обратно. Наконец, шлюзы API часто выступают в роли каталога доступных сервисов, помогая пользователям обнаруживать доступные функции в системе. В качестве недостатка шлюз API добавляет еще одну косвенность, возможное узкое место и потенциальную единую точку отказа.
С точки зрения традиционных шаблонов объектно-ориентированного проектирования, шлюз API действует как фасад для координации доступа ко всей подсистеме, он действует как адаптер для сокрытия различных протоколов, используемых в серверной части различными службами от клиентов и действует как прокси для реализации таких функций, как проверка подлинности, кэширование и обработка ошибок.
Пакетная обработка
Пакетная обработка относится к шаблону выполнения вычислений (включая преобразования) над очень большими объемами данных. В отличие от служб, пакетные задания не запускаются отдельными запросами и не дают немедленного ответа. Вместо этого они, как правило, выполняют длительные вычисления и записывают результаты в какое-либо решение для хранения данных, откуда к ним можно получить доступ после завершения задания.
Пакетная обработка в машинном обучении. В контексте машинного обучения пакетные задания часто используются для подготовки больших объемов обучающих данных в рамках выполнения конвейера машинного обучения при обучении модели (сбор данных, очистка данных). , проектирование признаков). Пакетную обработку также можно использовать для вывода модели для каждого отдельного элемента в большом наборе данных, например, чтобы использовать модель обнаружения объектов для определения ключевых слов для всех фотографий, хранящихся в нашем фотосервисе.
Этот стиль пакетной обработки является очень распространенной стратегией извлечения информации из огромных объемов неструктурированных данных в озере данных, например, для извлечения количества просмотров или удаленных фотографий из огромных объемов необработанных данных журнала. Шаги ETL в хранилищах данных также могут выполняться как распределенные пакетные процессы.
Структура заданий пакетной обработки. Чтобы эффективно выполнять пакетные вычисления в масштабе, важно разбить их на этапы, каждый из которых может быть выполнен на подмножестве данных перед объединением результатов. Таким образом, части вычислений могут выполняться параллельно на разных машинах с разными подмножествами данных. Общие шаги включают извлечение информации из необработанных данных, фильтрацию данных и агрегирование данных.
Рассмотрим пример определения наиболее просматриваемых фотографий на пользователя из очень больших объемов данных журнала, созданных многими экземплярами веб-сервера, на котором размещена серверная часть фотосервиса. Предполагая, что журнал очень большой и уже разбит на разделы, мы можем разделить часть работы для независимой обработки каждого раздела по отдельности: отфильтровать все строки файла журнала, чтобы включить только те, которые относятся к просмотру фотографий, извлечению имени файла и пользователя, и подсчет количества просмотров для каждого. После завершения этой работы над каждым разделом файла журнала промежуточные результаты можно объединить, отсортировать и сгруппировать для определения окончательных результатов. Если промежуточных результатов много, эти шаги можно распараллелить аналогично исходному параллельному анализу. Окончательные результаты в конечном итоге записываются в новый файл.
Наиболее распространенной парадигмой пакетной обработки является MapReduce, которая предоставляет общую модель программирования и соответствующую инфраструктуру для выполнения пакетных заданий в масштабе, например, реализованную в Apache Hadoop с открытым исходным кодом. Как следует из названий, вдохновленных функциональным программированием, шаги map выполняют вычисления без побочных эффектов для одной строки данных за раз (например, фильтрацию, извлечение данных, преобразование данных) для создания пары "ключ-значение". пары в качестве промежуточных результатов. Функция карты может применяться к разным строкам данных параллельно (на одной или нескольких машинах), а промежуточные результаты просто агрегируются, группируются по общим ключам. Промежуточные результаты обычно намного меньше исходных данных. Затем на шагах reduce объединяются различные промежуточные значения, вычисленные для ключа из разных подмножеств данных (например, когда ключи представляют собой имена файлов, а значения загрузок, шаг reduce выводит sum per key), снова производя промежуточный результат пар ключ-значение. Шаг сокращения также может выполняться параллельно для разных подмножеств промежуточных результатов, которые используют один и тот же ключ. Несколько шагов map и reduce могут быть упорядочены по-разному. Все шаги должны быть свободны от побочных эффектов, производя выходные данные только на основе входных данных таким образом, чтобы их можно было повторить, если это необходимо.
Как только вычисления выражены в стиле MapReduce, инфраструктура пакетных вычислений может планировать и организовывать вычисления в заданном порядке. Обычно он выделяет вычислительные ресурсы рядом с местом хранения разделов данных, параллельно выполняет шаг map, передает и группирует промежуточные результаты таким образом, чтобы шаги сокращения могли получить доступ ко всем результатам с одним и тем же ключом (называемым перемешать в терминологии MapReduce), а затем выполнить шаг reduce после того, как все необходимые промежуточные результаты будут готовы, опять же, возможно, в параллельном разделении по ключу. Этот процесс повторяется во всей последовательности шагов map и reduce пакетного задания. Помимо управления промежуточными результатами и выполнения каждого шага в заданной последовательности после завершения предыдущих шагов, инфраструктура будет обнаруживать сбои на отдельных шагах и перезапускать незавершенные вычисления. Инфраструктура обычно также управляет несколькими одновременными пакетными заданиями, организуя различные этапы нескольких заданий на многих машинах.
Перенос вычислений в данные. Основная идея вычислений в стиле MapReduce заключается в том, что перенос вычислений дешевле перемещения данных. Учитывая огромный размер некоторых наборов данных, было бы слишком дорого для процесса считывать все данные по сети. Обычно это справедливо даже в том случае, если для вычислений требуются большие модели с машинным обучением в службе вывода моделей. Поэтому обычно вычисления выполняются на машинах (или рядом с ними), где хранятся данные, и передаются только гораздо меньшие промежуточные результаты. Репликация данных обеспечивает дополнительную гибкость, поскольку вычисления могут выполняться на любой машине, имеющей реплику соответствующих данных.
Помимо MapReduce. Современные механизмы потоков данных, такие как Apache Spark, Tez или Flink, используют тот же подход, что и старая инфраструктура MapReduce, но обеспечивают большую гибкость модели программирования, возлагая больше ответственности на инфраструктура, чтобы решить, как разделить вычисления и где они выполняются. В наши дни они довольно распространены для программирования больших пакетных заданий в проектах машинного обучения и широко поддерживаются коммерческими облачными предложениями, такими как Databricks.
Потоковая обработка
Потоковая обработка предлагает совсем другой подход к обработке данных в масштабе, чем пакетная обработка. В то время как пакетная обработка выполняет вычисления на большом моментальном снимке данных, потенциально в течение длительного времени, потоковая обработка обрабатывает данные непрерывно по мере их поступления. Данные, подлежащие обработке, обычно вводятся в очередь и обрабатываются в основном в порядке поступления. Ввод данных в очередь и обработка данных из очереди могут быть распараллелены для масштабирования пропускной способности.
Обзор потоковой обработки. Проекты потоковой обработки берут свое начало в архитектурах, основанных на событиях, также известных как архитектуры в стиле передачи сообщений, архитектуры публикации-подписки или архитектуры производитель-потребитель. Терминология различается в разных реализациях, но концепции схожи: брокер сообщений хранит список тем. Компоненты в системе могут действовать (а) как производители, которые отправляют сообщения брокеру для темы, или (б) как потребители, которые подписываются на тему, чтобы получать новые сообщения, которые приезжать. Брокер пересылает все сообщения, полученные по теме, всем подписчикам этой темы, буферизуя сообщения в очереди, если скорость производства и потребления различаются. Этот дизайн отделяет производителей от потребителей, поскольку производителю не нужно знать, какие компоненты потребляют сообщение или даже сколько. Обратите внимание, что в отличие от услуг производитель не ожидает и не ждет ответа; как только сообщение отправлено, их ответственность выполнена; в лучшем случае они могут подписаться на потенциальные ответные сообщения по другой теме.
Этот стиль системной архитектуры посредством передачи сообщений распространен в различных контекстах, обычно когда компоненты постоянно реагируют на новые события или новые данные. При программировании пользовательских интерфейсов компоненты часто подписываются на такие события, как нажатие кнопки или мыши, и реагируют, когда такое событие происходит. В бизнес-системах сообщения могут относиться к бизнес-событиям, таким как продажи, которые могут инициировать множество других независимых действий в системе, таких как запись продажи, отправка электронной почты, обновление профиля пользователя и ведение журнала. В контексте машинного обучения сообщения могут относиться ко всем видам данных, передаваемых в системе, включая обучающие данные и пользовательские данные для вывода модели.
Потоковая обработка обычно имеет гораздо меньшую пропускную способность, чем пакетная обработка, но гораздо большую задержку. Вместо того, чтобы ждать часами или днями результата по большому объему данных, мы получаем результаты практически в реальном времени по мере поступления небольшого количества дополнительных данных.
Потоковая обработка и машинное обучение. Системы с поддержкой машинного обучения могут рассматривать потоковую обработку как важную стратегию масштабирования нескольких различных частей системы.
Системы, которые постоянно собирают обучающие данные, например, из файлов журналов, действий пользователей или путем непрерывного сбора API, могут использовать брокеры сообщений для направления данных через конвейер машинного обучения. В источнике каждая новая точка данных может быть передана брокеру в виде сообщения. Затем код очистки данных может обрабатывать каждую точку данных, отправляя полученные данные в новую тему. Впоследствии код разработки признаков может обрабатывать очищенные данные, по одной строке за раз, извлекать признаки и записывать полученные векторы признаков обратно в новую тему. Оттуда новый потребитель может получить векторы признаков и записать их в базу данных для последующего обучения в пакетном процессе. Важно отметить, что все шаги могут выполняться с разной пропускной способностью, при этом архитектура потоковой обработки позволяет независимо масштабировать каждый шаг. Например, если разработка функций обходится дорого, мы можем просто добавить больше компьютеров, чтобы облегчить выполнение этого шага.
Потоковая обработка особенно хорошо подходит для систем, которые постоянно обучаются с новыми данными, используя алгоритмы поэтапного машинного обучения. Здесь компонент обучения модели может подписаться на тему, предоставляющую новые обучающие данные (например, векторы признаков) и постоянно обновлять модель. Например, модель, обнаруживающая друзей на фотографиях, может постоянно обновляться каждый раз, когда пользователь отмечает изображение или подтверждает предложение модели.
Потоковая обработка также может использоваться для вывода модели, когда прогнозы модели должны отражаться в данных вскоре после того, как они были добавлены или изменены, но прогноз не требуется немедленно. В нашем рабочем примере мы хотим добавить ключевые слова к каждому изображению вскоре после его загрузки, но нам не нужны ключевые слова сразу для ответа. В этом случае мы можем добавить новые фотографии в качестве сообщений в очередь тем и позволить рабочим процессам вывода моделей обрабатывать эти сообщения по одному, причем многие рабочие процессы будут выполняться параллельно. С переменной нагрузкой в очереди сообщений мы можем динамически выделять больше или меньше воркеров по мере необходимости.
Наконец, потоковая обработка может использоваться для сбора и анализа данных телеметрии. Система может передавать данные журнала о выводе модели и взаимодействиях с пользователем в брокер сообщений, откуда инфраструктура мониторинга в качестве потребителя анализирует события и создает отчеты о качестве модели в скользящем окне. Например, мы можем отслеживать, как часто пользователь принимает предложенных друзей, или анализировать, как часто пользователь выполняет несколько поисковых запросов в короткой последовательности, не нажимая ни на одну фотографию, используемую в качестве прокси для того, чтобы быть недовольным исходными результатами поиска (подробнее об анализе телеметрии). в главе Обеспечение качества в производстве).
Инфраструктура потоковой обработки. Инфраструктура потоковой обработки обычно берет на себя значительную ответственность и является ключевым компонентом для масштабирования системы. Можно внедрить собственные брокеры сообщений или создать их поверх баз данных, но специализированная инфраструктура, как правило, сильно оптимизирована, легко настраивается и относительно легко внедряется. Например, типичные реализации обеспечивают следующие функциональные возможности:
- Брокер сообщений может быть распределен сам для достижения более высокой пропускной способности, используя различные стратегии секционирования и репликации. Некоторые реализации полностью соединяют производителей и потребителей без центрального посредника.
- Для ускорения обработки большая часть инфраструктуры поддерживает несколько экземпляров потребителей для параллельной обработки сообщений из темы, где каждый экземпляр получает подмножество сообщений. Здесь брокер действует как балансировщик нагрузки. Точно так же несколько экземпляров производителей могут писать в одну и ту же тему.
- Мониторинг количества буферизованных сообщений или задержки, с которой обрабатываются сообщения, может использоваться инфраструктурой для автоматической настройки количества экземпляров.
- Брокер отслеживает, какие сообщения уже были получены экземплярами каждого потребителя, чтобы пересылать только новые сообщения, даже если несколько разных потребителей обрабатывают сообщения с разной скоростью.
- Брокер может сохранять буферизованные сообщения для восстановления после сбоев. Он может удалять сообщения после их получения всеми потребителями или по истечении максимального времени. В качестве альтернативы он может хранить старые сообщения для новых потребителей, чтобы они могли получать сообщения, отправленные до того, как они изначально подписались (добавляя элемент постоянного хранения данных, знакомый по базам данных).
- Различные реализации предлагают разные стратегии обработки ошибок. Производители могут запрашивать или не запрашивать подтверждение того, что их сообщение действительно было получено. Потребители могут подтвердить, что они получили сообщение или что они завершили обработку сообщения, что позволяет использовать различные схемы повторных попыток в случае сбоев.
Разработчикам, использующим инфраструктуру потоковой обработки, необходимо принимать важные решения о том, как обрабатывать ошибки. В тех случаях, когда для пакетной обработки требуется, чтобы этапы вычислений были свободны от побочных эффектов, так что части вычислений можно было бы повторить при необходимости, разработчики, использующие приложения потоковой обработки, имеют больше гибкости, но и больше ответственности. В зависимости от того, как настроен или используется брокер, разработчики потребителей могут гарантировать либо (а) что каждое сообщение обрабатывается по крайней мере один раз, хотя оно может обрабатываться несколько раз, когда система восстанавливается после ошибок, или (б) что каждое сообщение обрабатывается не более одного раза, а сообщения могут быть потеряны из-за ошибок — гарантировать однократную обработку невозможно. В нашем примере с работающими фотографиями потерянные сообщения, вероятно, не имеют большого значения для обновления количества фотографий (не более одного раза), тогда как многократное обнаружение объектов не является проблемой для пометки изображений ключевыми словами (хотя бы один раз). Если потребители выполняют важные транзакции, такие как снятие средств с кредитных карт за заказанные фотопечати, каждое сообщение, вероятно, должно быть обработано по крайней мере один раз, но внешние механизмы (например, основанные на уникальных идентификаторах транзакций) должны гарантировать, что многократная обработка сообщения не приведет к на несколько платежей по кредитной карте.
В наши дни Apache Kafka является популярным выбором реализации брокера для потоковой обработки. Он рассчитан на высокую пропускную способность и хорошо масштабируется за счет распространения самого брокера сообщений. Однако существует множество конкурирующих реализаций брокеров сообщений, и большинство коммерческих поставщиков облачных услуг также предлагают свои собственные.
Документирование потоков данных. Распределенная связь по задачам обеспечивает большую гибкость при составлении систем из слабосвязанных компонентов, но известно, что получающиеся системные архитектуры сложны для понимания и отладки. В традиционных сервис-ориентированных системах клиент отправляет запрос и ждет ответа, но при потоковой обработке производитель отправляет сообщение в пустоту, и потребитель надеется, что кто-то создаст правильные сообщения в правильном формате. Одно сообщение может обрабатываться многими потребителями, которые снова создают новые сообщения, что приводит к длинным конвейерам и структурам графов.
Документирование потоков данных в системе для определения того, какие компоненты создают и потребляют какие сообщения по разным темам, помогает сохранять обзор. Большинство брокеров сообщений предоставляют некоторую поддержку мониторинга, чтобы отслеживать, какие темы используются, сколько сообщений создается в каждой теме и насколько далеко отстает каждый потребитель; часто также полезно наблюдать за тенденциями для всего этого. Кроме того, часто целесообразно документировать формат данных сообщений в различных темах и явно управлять его развитием. Некоторые брокеры сообщений явно поддерживают принудительные схемы, а кодирование на основе схемы (см. выше) может быть добавлено для тех, которые этого не делают.
Лямбда Архитектура
Лямбда-архитектура — это недавнее и популярное сочетание трех стратегий: службы, пакетная обработка и потоковая обработка. В классической настройке анализа больших данных он использует пакетную обработку для выполнения точных вычислений по всем данным через равные промежутки времени (например, суммирование всех бизнес-транзакций по регионам), потоковую обработку для постепенного обновления результатов (например, добавление последних транзакций к итоговым значениям). ) и службы для ответов на запросы с самыми последними результатами (например, ответы с общим количеством транзакций для определенного региона). В контексте машинного обучения может использоваться пакетная обработка для регулярного обучения моделей на всех данных, потоковая обработка для постепенного обновления моделей новыми данными между пакетными заданиями и службы для ответа на запросы вывода модели с последней моделью.
Неизменяемые данные только для добавления (источник событий). Архитектура лямбда использует данные моделирования как поток событий редактирования, хранящихся в журнале событий только для добавления, который часто называется источник событий. То есть вместо обновления отдельных строк в таблице мы записываем событие изменения отдельной строки. Хранение данных в виде списка изменений имеет давнюю традицию в некоторых системах контроля версий (см. главу Управление версиями, происхождение и воспроизводимость) и в качестве механизма восстановления в реализациях баз данных.
Этот стиль хранения данных позволяет системе реконструировать данные в любой момент времени: самая последняя версия данных может быть восстановлена путем воспроизведения всех событий изменения с самого начала. Более старые версии можно восстановить, воспроизведя историю событий только частично. Хранение истории данных может быть полезно при попытке проанализировать прошлые действия, например, как часто пользователи меняли описание фотографии или какие фотографии они редактировали или удаляли. Недостатки этого подхода заключаются в том, что воспроизведение истории для извлечения последних данных может быть очень дорогостоящим и что требуется дополнительное пространство для хранения, особенно если изменения происходят часто. Как правило, представление самых последних данных хранится в памяти или на диске для быстрого доступа, а также могут сохраняться моментальные снимки в определенное время для ускорения обработки.
addPhoto(id=133422131, user=54351, path="/st/u211/1U6uFl47Fy.jpg", date="2021-12-03T09:18:32.124Z") updatePhotoData(id=133422131, user=54351, title="Sunset") replacePhoto(id=133422131, user=54351, path="/st/x594/vipxBMFlLF.jpg", operation="/filter/palma") deletePhoto(id=133422131, user=54351)
Пример журнала событий, описывающих изменения данных.
Некоторые части системы могут уже естественным образом создавать данные в форме только для добавления, например, файлы журналов и показания датчиков. Озера данных также хорошо подходят для лямбда-архитектуры, поскольку данные, хранящиеся в озерах данных, почти всегда доступны только для добавления. Значения, полученные из таких данных, например, как часто просматривались фотографии или векторы признаков для задания машинного обучения, часто будут кэшироваться в базе данных, чтобы избежать повторной обработки всего набора данных.
Три уровня лямбда-архитектуры. Лямбда-архитектура состоит из трех уровней: пакетного уровня, уровня скорости и уровня обслуживания.
Пакетный уровень выполняет вычисления по всем данным как пакетный процесс. Например, он может вычислить количество просмотров каждого изображения из данных журнала или обучить модель обнаружения друзей на всех доступных обучающих фотографиях. Это предназначено для больших вычислений на моментальном снимке всего набора данных (в определенный момент времени) и использует инфраструктуру пакетных вычислений, описанную выше. Пакетная обработка хорошо подходит для обработки больших объемов данных в озере данных. Уровень пакетной обработки обычно запускает большое пакетное задание через равные промежутки времени, например, ежедневно или еженедельно.
Уровень скорости использует потоковую обработку для обновления вычислений каждой дополнительной строкой данных, которая была добавлена в хранилище данных только для добавления с момента моментального снимка, на котором было выполнено последнее пакетное задание. Например, он может обновлять количество просмотров каждой фотографии или постепенно обновлять модель обнаружения друзей дополнительными данными по мере того, как пользователи отмечают фотографии или подтверждают предложения. Инкрементальные вычисления часто являются приближенными или более сложными для распараллеливания, и разработчики систем часто допускают незначительные неточности, поскольку результат будет регулярно заменяться более точными вычислениями из пакетного задания. Например, несколько процессов-потребителей для обновления счетчиков просмотров могут перезаписывать изменения друг друга — хотя этого можно было бы избежать с помощью распределенных транзакций, гораздо более высокая пропускная способность за счет избегания накладных расходов на транзакции может считаться более важной, а незначительные неточности в подсчетах просмотров не считаются проблемой, поскольку точное количество просмотров в любом случае будет пересчитано в следующем пакетном задании. Точно так же добавочные обновления моделей с машинным обучением, особенно при распределенном вычислении, могут иметь более низкое качество, чем обучение всей модели с нуля (особенно если обучающие данные неоднократно изменяются, поскольку сложно забыть о конкретной текущей модели). измененная точка обучающих данных) — но, тем не менее, это, вероятно, обеспечивает лучшие прогнозы, чем использование устаревших моделей из последнего пакетного задания.
Наконец, обслуживающий уровень предоставляет клиентам результаты пакетного и скоростного уровней, зная, как получить доступ к внутренним результатам вычислений. В наших примерах он может отвечать подсчетом просмотров для определенных фотографий или выполнять вывод модели с последней версией модели обнаружения друзей.
Обратите внимание, как лямбда-архитектура сочетает в себе сильные стороны различных конструкций, поскольку каждый уровень фокусируется на разных качествах: пакетный уровень выполняет крупномасштабные вычисления и дает наиболее точные результаты, но результаты могут быть уже устаревшими к моменту завершения задания. Уровень скорости фокусируется на обновлениях почти в реальном времени при более низкой пропускной способности, потенциально жертвуя некоторой точностью с приближениями. Уровень обслуживания фокусируется на ответах на запросы с малым временем отклика на основе результатов или моделей, предварительно рассчитанных с помощью пакетной и потоковой обработки. Если она соответствует системным требованиям, такая архитектура может позволить сбалансировать задержку, пропускную способность и отказоустойчивость в масштабе.
Алгоритмы распределенного машинного обучения
Многие алгоритмы машинного обучения требуют интенсивных вычислений, но особенно подходы глубокого обучения часто используются в значительных масштабах, поглощая большие объемы данных и выполняя вычисления в течение недель или лет на специализированном оборудовании. Как следствие, многие современные реализации алгоритмов машинного обучения используют преимущества многоядерных машин и могут распределять задания по обучению между несколькими машинами.
Например, OpenAI GPT-3, выпущенная в 2020 году, представляет собой модель глубокого обучения с 96 слоями и 175 миллиардами весов. Во время обучения и логического вывода сама модель занимает 700 гигабайт памяти. Он был обучен на 570 гигабайтах сжатых текстовых данных. Подсчитано, что обучение заняло бы 355 лет, если бы оно проводилось последовательно на кластере с 285 000 ядер ЦП и 10 000 графических процессоров, созданном специально для больших задач машинного обучения. Основываясь на современных ценах на облачные ресурсы, было подсчитано, что однажды обучение этой модели стоило бы почти 5 миллионов долларов США.
Хотя мы не будем вдаваться в технические подробности распределенного машинного обучения, мы предоставим некоторую интуицию, основанную на общей стратегии распространения сервера параметров, представленной в TensorFlow.
По сути, как кратко описано в главе Атрибуты качества, глубокая нейронная сеть состоит из большого количества параметров, организованных в матрицы, по одной матрице на слой (параметры — это значения, которые необходимо изучить, в конечном итоге они являются константами в обученной модели). . Алгоритм обратного распространения, используемый для обучения, берет одну обучающую пару вход-выход, вычисляет прогноз для ввода (через последовательность матричных умножений с параметрами модели), а затем немного корректирует все параметры, чтобы подтолкнуть модель к ожидаемому результату. для этого ввода (в процессе, который требует больше математики). Этот процесс повторяется снова и снова с разными парами вход-выход, пока параметры не стабилизируются. Этот подход к постепенной настройке параметров называется градиентным спуском.
Для распространения градиентного спуска параметры модели копируются на несколько рабочих машин. Каждая рабочая машина возьмет подмножество пар ввода-вывода и локально выполнит градиентный спуск по параметрам модели, корректируя их на каждом этапе обучения. При работе с пакетом данных корректировки параметров различных рабочих процессов объединяются, так что все рабочие процессы начинают с одинаковыми параметрами для следующего пакета. Вместо того, чтобы все рабочие процессы координировали свои действия со всеми другими рабочими процессами, рабочие взаимодействуют с сервером, который хранит и объединяет параметры модели, сервером параметров. Дополнительным ключевым нововведением для экономии полосы пропускания для больших моделей является не передача обновлений для всех параметров модели на каждом шаге синхронизации (для GPT-3 это было бы 700 гигабайт после каждого пакета), а только передача различий параметров для (обычно нескольких) параметры с изменениями выше определенного порога. Кроме того, чтобы сбалансировать нагрузку, сам сервер параметров может быть распределен с несколькими серверами, каждый из которых хранит только подмножество параметров модели.
На высоком уровне этот подход похож на классическую пакетную обработку, при которой работа распределяется между разными работниками, каждый из которых обрабатывает подмножество данных, а затем интегрирует свои результаты («уменьшение» в map-reduce). В отличие от традиционных заданий пакетной обработки, объединение обновлений параметров и отправка только частичных обновлений приводит к приближениям и шуму, что делает обучение недетерминированным. Как и в инфраструктуре пакетной обработки, реализации обычно обеспечивают существенную поддержку для координации и планирования работы, управления сетевым трафиком, управления несколькими независимыми заданиями и обработки ошибок.
Стратегия обучения нейронных сетей градиентному спуску также, естественно, является пошаговой. Таким образом, обучение может выполняться и в режиме потоковой обработки, когда новые данные непрерывно поступают в процесс обучения, а последняя версия модели может быть получена с сервера параметров в любое время.
Планирование и анализ производительности
При эксплуатации больших и распределенных систем планирование и мониторинг имеют ключевое значение для обработки больших нагрузок. В идеале рабочие нагрузки планируются заранее, анализируя, какую работу можно легко масштабировать, запустив несколько экземпляров службы, и где могут возникнуть узкие места (часто в базах данных). Затем архитекторы программного обеспечения и инженеры по производительности могут выполнять моделирование или аналитические вычисления (например, с использованием теории очередей), чтобы оценить, как различные проекты системы будут масштабироваться до ее создания.
Чаще всего команды, инвестирующие в разработку, но не обладающие глубокими знаниями в области масштабируемых систем, выбирают технологии и проектируют систему с некоторым расчетом на будущую масштабируемость, но фактические проблемы с производительностью решаются, когда возникает необходимость. Как мы обсудим в главе Технический долг, это может быть оправдано разумным техническим долгом для быстрого выхода на рынок без слишком больших предварительных инвестиций.
На уровне компонентов существуют различные формы тестирования производительности, чтобы проверить, как компоненты ведут себя под нагрузкой и какую пропускную способность можно ожидать. Обычно такие инструменты, как Apache JMeter, отправляют фиксированное или возрастающее количество запросов к компоненту для измерения (а) времени отклика, (б) пропускной способности и © использования ресурсов при различных нагрузках. Например, нагрузочное тестирование проверяет, может ли компонент справиться с максимальной ожидаемой нагрузкой, тестирование масштабируемости исследует, как компонент ведет себя при увеличении нагрузки, тестирование выдержки исследует, остается ли компонент стабильным при перегрузке в течение некоторого времени, и стресс-тестирование намеренно перегружает всю систему, чтобы проверить постепенное снижение производительности и восстановление. Такие тесты также могут быть автоматизированы и выполняться в каждом выпуске, гарантируя, что изменение компонента не приведет к неожиданному замедлению работы всей системы.
На системном уровне существенное значение приобретает наблюдаемость посредством мониторинга производительности. С этой целью разработчики используют различные компоненты системы для отслеживания загрузки сервера и сети, вызовов между компонентами и времени их отклика, очередей сообщений в брокерах сообщений и многих других показателей производительности. Существуют различные готовые инструменты для выполнения инструментовки, сбора результатов и создания отчетов и информационных панелей, таких как AppDynamics. Настройка мониторов для среднего времени отклика, пропускной способности и времени простоя различных частей системы несложна, но требует некоторых затрат на первоначальную настройку.
Надежная инфраструктура мониторинга производительности обеспечивает прочную основу для наблюдения за долгосрочными тенденциями, для автоматизации добавления и удаления дополнительных серверов по мере необходимости, для выявления проблем до того, как они станут серьезными, для оповещения о проблемах, требующих ручного вмешательства, а также для автоматизации выпусков и провести A/B-тестирование (см. раздел Обеспечение качества в производстве).
Краткое содержание
Машинное обучение часто используется в масштабируемых системах с интенсивным использованием данных. Обычно такие системы превосходят возможности одной машины с огромными объемами данных и высокими требованиями к вычислительным ресурсам, будь то обработка данных и разработка функций, обучение модели или вывод модели. В таких условиях распределение памяти и вычислений становится неизбежным.
Распределенные системы сопряжены со значительными инженерными проблемами. К счастью, разработчикам не нужно начинать с нуля, они могут опираться на мощные абстракции и соответствующую инфраструктуру, которые обрабатывают многие сложные части для создания действительно масштабируемых и наблюдаемых систем. Тем не менее, базовое понимание ключевых концепций и компромиссов важно для выбора подходящих методов и систем проектирования, которые действительно могут масштабироваться.
Точно так же, как инженеры-программисты в производственных проектах с компонентами машинного обучения извлекут выгоду из понимания ключевых концепций машинного обучения, они и специалисты по данным извлекут выгоду из понимания ключевых абстракций, предоставляемых для баз данных, распределенного хранилища данных и распределенных вычислений, включая службы, пакетные вычисления, и потоковая обработка. Кроме того, понимание концепций, лежащих в основе модных словечек, таких как озера данных и лямбда-архитектура, помогает определить, подходят ли они для данного проекта. Независимо от того, будут ли в конечном итоге инженеры-программисты, специалисты по данным или специальные инженеры и операторы данных, которые будут выбирать и использовать определенные технологии, установление общего понимания и проектирование систем с учетом компромиссов и ограничений поможет всей команде работать вместе над созданием масштабируемой системы.
Многие из этих абстракций и концепций инфраструктуры также описываются как архитектурные стили, архитектурные шаблоны или шаблоны проектирования, включая микросервисную архитектуру, архитектуру публикации и подписки (потоковая обработка), лямбда-архитектура, озера данных и шаблон пакетного обслуживания.
Дальнейшее чтение
- Книга, содержащая отличный обзор технических проблем и решений распределенного управления и обработки данных, с упором на принципы и компромиссы, а не на конкретные реализации: Martin Kleppmann. Проектирование приложений, интенсивно использующих данные. ОРейли. 2017.
- Во многих книгах рассматриваются конкретные технологические стеки и рассматриваются конкретные примеры проектирования и реализации. Например, следующая книга хорошо описывает составляющие лямбда-архитектуры в экосистеме Java (HDFS, Thrift, Hadoop, Cassandra, Storm): Уоррен, Джеймс и Натан Марц. Большие данные: принципы и лучшие практики масштабируемых систем данных реального времени. Manning, 2015. В этой книге рассматривается другой стек технологий в Scala (Spark, Akka, MLlib): Smith, Jeffrey. Системы машинного обучения: масштабируемые проекты. Мэннинг, 2018.
- Списки инструментов ETL, которые могут заинтересовать инженеров, вкладывающих больше средств в извлечение, преобразование и перемещение данных в системах: https://github.com/pawl/awesome-etl, https://www.softwaretestinghelp. .com/best-etl-tools/», https://www.scrapehero.com/best-data-management-etl-tools/
- Подробное обсуждение озер данных и различных архитектур и проектных решений, связанных с ними: Sawadogo, Pegdwendé и Jérôme Darmont. «Об архитектуре озер данных и управлении метаданными.» Журнал интеллектуальных информационных систем 56, вып. 1 (2021): 97–120.
- Приглашенный доклад с хорошим описанием реалий решений для хранения данных во многих компаниях и сложности извлечения и интеграции многих источников данных: Молхам Ареф Бизнес-системы с машинным обучением