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

На самом деле, большинство наших конкурентов признают эту ситуацию и пошли по пути создания программных продуктов с безумным количеством меню, кнопок и опций, всё это — в отчаянной попытке справиться со всеми проблемами цепей поставок. К сожалению, этот путь приводит к появлению программных чудовищ, которые оборачиваются эффектными провалами при масштабном использовании. Я называю этот антипаттерн Неэвклидовским Ужасом.
Таким образом, столкнувшись с классом проблем — а именно, с проблемами цепей поставок, которые просто невозможно решить одним универсальным приложением, мы начали, отчасти случайно1, решать мета-проблему: как создавать кастомизированные приложения, где каждое будет направлено на решение одной конкретной проблемы в заданной ситуации, например, оптимизацию пополнения запасов для конкретной компании.
Создание кастомизированного программного обеспечения для бизнеса — не новость. Программная индустрия зародилась именно так в 60-х годах, а затем в 80-х перешла к доминирующей модели shrinkwrap, которую мы знаем сегодня. Как правило, кастомизированное ПО обладает множеством нежелательных свойств по сравнению с shrinkwrap: более высокие первоначальные затраты, длительная настройка, высокие эксплуатационные расходы, повышенные риски и т.д.
Тем не менее, опыт, накопленный в первые годы работы Lokad, показал, что в оптимизации цепей поставок кастомизированное ПО имело одно ключевое преимущество: оно действительно давало отличные результаты. Действительно, в то время как наше оригинальное приложение показывало, в лучшем случае, сносные результаты2, вне зависимости от точности прогнозов, эти прототипы часто демонстрировали отличные показатели. Более того, единственное, что требовалось, — это крайняя специализация данного программного продукта, предназначенного для решения конкретной проблемы.
Исчерпав, казалось бы, все альтернативы, мы пришли к выводу, что создание кастомизированных приложений — единственный выход. Однако масштабируемость — как обеспечить доставку множества приложений, и поддерживаемость — как держать эксплуатационные расходы под контролем, оставались двумя ключевыми задачами. Во-первых, нам нужно было выбрать язык программирования. Тогда мы рассматривали множество вариантов: R, Python, JavaScript, Lua, C#, а также создание нашего собственного предметно-ориентированного языка программирования (DSL), который позже стал известен как Envision. Обсуждение всех плюсов и минусов этих вариантов оказалось бы довольно утомительным3, поэтому для ясности обсуждение свелось к выбору между Python и Envision, при этом Python рассматривался как главный конкурент собственной разработке DSL.
Python был привлекателен благодаря своей простоте и богатой экосистеме сторонних библиотек, особенно в области машинного обучения4. Кроме того, для Lokad это был экономичный вариант: поскольку Python и практически все его популярные библиотеки являются open source, мы могли бы просто переупаковать узкий набор Python, включив в белый список несколько десятков отобранных пакетов, и на этом все. Большая часть работы в Lokad была бы сосредоточена на создании PaaS-решения на базе Python в духе Heroku, но максимально адаптированного к проблемам цепей поставок.
Однако вот критерий, который мы рассматривали: разумно ли ожидать, что бизнес-аналитик — впоследствии называемый специалистом по цепям поставок — работающий 1 день в неделю в течение 6 месяцев, сможет создать готовое к производству приложение для решения критически важной проблемы цепей поставок, например, оптимизации пополнения запасов для компании с оборотом $10M? Глядя на вариант с Python, было ясно, что мы никогда не сможем приблизиться к такому уровню операционной эффективности.
Прежде всего, Python требует участия инженеров-программистов. Действительно, Python, как и любой полноценный язык программирования, обнажает массу технических тонкостей тому, кто пишет на нем код. Хотя роль специалиста по цепям поставок была формализована лишь позже, у нас с самого начала было предчувствие, что даже с учетом наличия талантливых специалистов, ожидать от них, чтобы они были одновременно экспертами в области оптимизации цепей поставок и в программировании, — это слишком амбициозно. Нам нужны были программные возможности, доступные широкому кругу технически подкованных людей, а не только профессиональным разработчикам.
Таким образом, мы создали Envision как язык, способный устранить целые классы технических проблем, которые неизбежны при использовании Python. Например:
- Объекты могут быть null, даты могут быть абсурдно отдалены как в прошлом, так и в будущем, значение NaN может охотно распространяться по вашему каналу данных, строки могут становиться абсурдно длинными… В цепях поставок эти «особенности» — всего лишь проблемы, ожидающие своего часа.
- Объектно-ориентированные элементы (то есть классы) гарантированно будут использоваться неправильно5, и то же можно сказать о пользовательских исключениях или регулярных выражениях. Само их наличие является, в лучшем случае, нездоровым отвлечением.
- Многие базовые операции, такие как разбор различных табличных файлов (включая Excel), не являются частью языка и требуют использования множества разнородных пакетов, каждый из которых обладает своими техническими особенностями.
Ни один из этих классов технических проблем нельзя устранить в Python, не нанося ущерба самому языку. Envision, как язык программирования, доступен для специалистов по цепям поставок (в отличие от специалистов по программированию) исключительно благодаря своему предельно четкому фокусу на прогнозной оптимизации цепей поставок.
Вспомните, когда в последний раз вам приходилось выполнять расчеты в Excel, и представьте, что вы диктуете все изменения в этой таблице по телефону, не имея возможности видеть сам файл. Вот как выглядит инициатива по оптимизации цепей поставок, когда ею управляют практики, а реализуют программисты (не специалисты по цепям поставок). Бизнес тратит огромное количество времени на то, чтобы донести свои требования до IT, а IT, в свою очередь, тратит огромное количество времени, пытаясь понять, чего же на самом деле хочет бизнес. После десятилетнего опыта работы в Lokad я пришёл к выводу, что привлечение разработчиков, не являющихся специалистами по цепям поставок, для реализации инициатив по количественной оптимизации цепей поставок, увеличивает затраты как минимум в 5 раз, независимо от того, насколько гибкой и талантливой может быть команда.
Во-вторых, затраты на обслуживание поспешно созданных прототипов на Python взлетают до небес. За пределами IT-индустрии немногие понимают, что программная инженерия6 в основном сводится к контролю затрат на обслуживание. При этом решение задач оптимизации цепей поставок — процесс запутанный: данные из множества (условно) надежных систем должны надежно передаваться по каналу, несовершенные и постоянно меняющиеся процессы необходимо документировать и моделировать, а оптимизационные метрики отражают бизнес-стратегию в состоянии постоянных изменений. В результате любое программное обеспечение, созданное для решения задач оптимизации цепей поставок, неизбежно несет в себе огромную дозу специфической сложности, просто пытаясь справиться с тем, что на нас обрушивается.
Тем не менее, время имеет решающее значение. Нет смысла иметь идеальный план для производственного процесса прошлого года. Как правило, можно смело предположить, что в тот день, когда программный прототип начнет работать, его обязательно переведут в производство в течение нескольких недель, независимо от того, насколько качественно он написан.
Ожидать, что высшее руководство одобрит 6-месячную задержку для переписывания прототипа с целью доведения его до уровня, пригодного для производства с точки зрения обслуживания, — это мечтательность. Однако вывод поспешного прототипа на Python в производство является рецептом для эпических эксплуатационных затрат, битвы с неустанным потоком ошибок, которые придется временно фиксировать 24/7.
Таким образом, единственный практический способ сохранить стабильность производства — написать прототип на языке программирования, который с самого начала обеспечивает высокий уровень корректности по замыслу. Например, в отличие от Python, Envision обеспечивает:
- Гарантированное конечное время выполнения на этапе компиляции: при обработке нескольких терабайт данных становится чрезвычайно утомительно ждать часы, прежде чем понять, что вычисление так и не завершится. Ограниченное потребление памяти, гарантированное на этапе компиляции: борьба с ошибками нехватки памяти в ночном производственном цикле — вовсе не весёлое занятие и, на практике, серьёзно нарушает работу.
- Атомарные операции чтения и записи: Envision по замыслу предотвращает одновременные чтения и записи в файловой системе, даже когда файлы передаются через FTP во время выполнения скриптов. Файловая система, лежащая в основе Envision, фактически представляет собой адаптированный под гигантские плоские файлы Git. Без надлежащего версионирования данных многие ошибки превращаются в хейзенбаги: к тому времени, как кто-либо начнет разбираться с проблемой, данные обновятся, и её уже нельзя будет воспроизвести.
- Распределенное масштабирование выполнения программы на облаке вычислительных ресурсов, устраняющее все препятствия для параллельной обработки, которые неизбежны, как только объем данных превышает несколько десятков гигабайт.
Общие языки программирования обеспечивают крайне малый уровень корректности по замыслу, а Python, опираясь на механизм позднего связывания, предлагает чрезвычайно скудные возможности в этой области. Даже если рассматривать лучшие альтернативы — с точки зрения корректности по замыслу — такие как Rust, они никак не удовлетворяют требованиям оптимизации цепей поставок.
Вот ещё несколько областей, где Envision превосходит Python:
Глубокая защита: как только кто-либо начинает писать код в вашей организации, если не принять очень специальных мер предосторожности7, их код становится немедленной ответственностью с точки зрения IT-безопасности. При использовании Python практически возможно сделать что угодно на машине, запускающей скрипт. Правильное создание песочницы для Python на практике является чертовски сложной задачей. В частности, любая строка, сформированная скриптом, является потенциальным вектором для инъекций. Хотя SQL-инъекции широко известны, слишком немногие понимают, что даже простые текстовые файлы, такие как CSV, уязвимы для атак внедрения. Envision обеспечивает такой уровень безопасности, который просто невозможно воспроизвести с помощью Python. Утечки данных становятся все более частыми, и разбрасывание кусочков Python повсюду не улучшит IT-безопасность.
Прозрачная производительность: если программа работает неприемлемо медленно, то она вообще не должна компилироваться8. Если программа становится на одну строку короче, то она должна работать быстрее. Если изменяется только одна строка, то должна перерасчитываться только эта строка9 при повторном запуске на тех же данных. При компиляции компилятор должен нацеливаться не на конкретную машину, а на облако вычислительных ресурсов, обеспечивая автоматическую параллелизацию, основанную на данных. Envision значительно приближает достижение всех этих свойств по умолчанию, без каких-либо дополнительных усилий со стороны разработчика. В отличие от него, Python требует массового применения специализированных библиотек, чтобы хоть как-то приблизиться к этим свойствам.
Прозрачное обновление: в области программного обеспечения передовые технологии — это движущаяся цель. В 2010 году лучшим инструментарием для машинного обучения был SciPy (если можно так выразиться). В 2013 году — scikit. В 2016 году — Tensorflow. В 2017 году — Keras. В 2019 году — PyTorch. Существует мнение, что год рождения любого проекта можно определить, взглянув на его программный стек и зависимости. Действительно, как только вы запускаете свои скрипты на Python, вы неизбежно сталкиваетесь с множеством зависимостей, которые со временем могут устареть. В отличие от этого, с Envision мы активно используем автоматизированные переписывания кода10, чтобы «наследственные» скрипты оставались актуальными в условиях постоянно меняющегося языка.
Упакованный стек: скрипты на Python не могут существовать в вакууме11. Код необходимо версионировать (например, с помощью Git) с соблюдением прав доступа (например, GitHub). Им нужна среда для выполнения, которая не является вашей машиной (например, виртуальная машина на Linux в облаке). Для оркестрации канала данных требуется планировщик (например, AirFlow). Для подготовки данных необходим распределенный колоночный слой хранения (например, Spark). Для предиктивной аналитики нужен инструмент машинного обучения (например, TensorFlow). Для решения комбинаторных задач цепей поставок требуется инструмент оптимизации (например, GLPK). Исходные результаты необходимо где-то экспонировать для дальнейшего использования (например, SFTP-сервер). Специалистам по цепям поставок важно иметь возможность мониторить происходящее (например, через веб-интерфейс). Права доступа должны строго соблюдаться (например, Active Directory) и т.д. Envision объединяет всё это в одно мета-приложение, избавляя от необходимости собирать десятки программных компонентов для создания даже самого базового приложения.
Таким образом, хоть Python и является отличным языком, он не лишен недостатков:
- Вычислительная производительность оставляет желать лучшего, и прокладывать каждое вычисление через правильную библиотеку (например, NumPy), чтобы избежать катастрофически низкой производительности при обработке данных, — задача не из легких. Более того, использование множества библиотек создаёт значительное трение, когда данные приходится перемещать от одной к другой.
- Производительность работы с памятью тоже оставляет желать лучшего, и, в частности, сборка мусора Python с подсчетом ссылок уже устарела — а все более современные языки программирования, такие как Java, C# или JavaScript, используют трассировку вместо этого. При выполнении задач, требующих интенсивного использования памяти при обработке больших данных, это наносит ущерб.
- Управление пакетами в Python было хаотичным на протяжении давнего времени, и для правильной работы требуется специалист по пакетам. Кроме того, эту проблему усугубляют сложные обновления языка.
- Большинство (немногих) проверок корректности выполняются только во время выполнения, когда программа запускается, что вызывает бесконечное разочарование при интенсивной обработке данных. Очевидные проблемы проявляются только спустя несколько минут работы, снижая продуктивность.
В заключение, хотя Python великолепен (и это факт), он не является удовлетворительным решением для оптимизации цепочки поставок. Создание и сопровождение производственного приложения для машинного обучения на Python вполне возможно, но затраты значительны, и если ваша компания не готова иметь хотя бы небольшую команду инженеров-программистов, посвященную поддержке этого приложения, всё в итоге не обеспечит удовлетворительных результатов для вашей цепочки поставок.
Разработка Envision — специализированного языка программирования, предназначенного для предиктивной оптимизации цепочки поставок — не была нашим первым выбором. Это даже не был наш десятый выбор. Скорее, это было единственное работающее решение, которое у нас оказалось после исчерпывания долгого списка более традиционных альтернатив в течение пяти лет. Семь лет спустя и после работы с множеством клиентских компаний каждый новый клиент всё равно умудряется удивить нас, так или иначе, очередным поворотом в своей цепочке поставок, который мы никогда не смогли бы учесть с помощью классического подхода корпоративного ПО. Необходима была программируемость, но Python не был решением, которое нам требовалось.
-
Еще в 2013 году я был убежден, что можно создать удовлетворительное приложение для оптимизации цепочки поставок. Но столкновение с ценовыми проблемами как-то изменило наши планы и направило Lokad на путь создания собственного специализированного языка программирования. Изначально этот язык был предназначен только для оптимизации ценообразования, но вскоре мы поняли, что этот подход как раз и нужен для оптимизации цепочки поставок. ↩︎
-
Идея о том, что обеспечив превосходную точность прогнозирования можно в одиночку добиться превосходных результатов в цепочке поставок, вероятно, была одним из самых больших заблуждений, которые я имел при основании Lokad. Посмотрите этот выпуск Lokad TV, чтобы получить более сбалансированное представление об этом вопросе. ↩︎
-
Большинство проблем, которые мы выявили в Python, не связаны с самим Python, который является отличным языком программирования, а скорее с тем, что Python является универсальным языком программирования. ↩︎
-
Еще в 2013 году Python ещё не достиг того господства, которое он обрёл в последующие несколько лет в области машинного обучения, R всё ещё оставался сильным конкурентом, однако SciPy и NumPy, две отличные библиотеки, уже существовали и активно развивались. ↩︎
-
Ознакомьтесь с этой отличной лекцией Stop Writing Classes с Pycon 2012. Даже опытные инженеры-программисты часто допускают ошибки в этом вопросе. ↩︎
-
Инженерия программного обеспечения, в отличие от компьютерных наук. Первое занимается обеспечением работы производственных систем, а второе — решением сложных задач, таких как поиск более быстрых алгоритмов. ↩︎
-
К сожалению, в вопросах безопасности кода почти нет или нет заменителей систематического взаимного рецензирования кода. ↩︎
-
Из-за проблемы остановки вдумчивый читатель может предположить, что Envision не является полноценным по Тьюрингу языком. И действительно, Envision таковым не является. ↩︎
-
Envision основывается на разнице между графами вычислений и пытается минимизировать количество перерасчетов между инкрементальными изменениями, чтобы обеспечить очень быстрое прототипирование на больших наборах данных. Однако, в зависимости от ситуации, изменение одной строки может потребовать перерасчета всего скрипта. ↩︎
-
Автоматизированное переписывание кода чрезвычайно сложно для универсального языка программирования. Если язык и его стандартная библиотека не были разработаны с учетом этой задачи, автоматические инструменты обновления оказываются малоэффективными на практике. При разработке Envision мы знали, что допустим множество ошибок (и мы их и допустили), поэтому уделили большое внимание тому, чтобы язык был особенно пригоден для автоматизированного переписывания. На сегодняшний день с момента создания Envision нами было выполнено свыше 100 инкрементальных переписываний. ↩︎
-
Иронично, что «Batteries Included» («Аккумуляторы включены») является одним из девизов Python. Однако огромное количество «клея», необходимого для объединения всех элементов, требуемых для создания приложения, предназначенного для предиктивной оптимизации цепочки поставок, кажется пугающим. ↩︎