Pull to refresh

Comments 11

Не совсем понял цель. Зачем очередной велосипед, если уже давно есть Artifactory?

ПыСы это же не случай когда вайбкодеры без фундаментальных знаний даже “не знают того что они не знают” и поэтому изобретают велосипед? Ведь нет?)

Отличный вопрос про Atrifactory! Но тут идея совсем другая - это же не менеджер пакетов, коим является и Artifactory, и Nexus, напрмер. В splime вы публикуете функкцию, а не пакет со всей его обвязкой. Вызывающему не нужно ставить зависимости опубликованного объекта: демон собирает изолированное окружение (uv / venv / docker) и исполняет ноду там. И тот же самый вызов можно выполнить не только у себя локально, а на любой машине, заранее связанной с текущим пользователем.

Ниже сформулированили частично мой consern. Какие реальные продакшен-кейсы вы видите для данного решения?

Фреймворк и демон спроектированы изначально под три типа задач:
1) функция с неудобными зависимостями: скоринг на torch, например, который надо вызывать из лёгкого сервиса или ноутбука, не втаскивая тяжелые зависимости torch’а к вызывающему.
2) выполнить задачу там, где находятся данные: вызываешь ноду по имени, а исполняется она на машине с доступом к БД или какими-то конкретными ресурсами, причём вызывающему выдаётся execute, а не креды.
3) самое распространенное приземлённое - длинные пайплайны, падающие на N-м шаге из K: resume продолжает с места падения, не пересчитывая дорогой апстрим.

Сейчас в тестовом режиме гоняем в ML-процессах внутри команды для разработки и деплоя моделей.

Решение с пакетом было верным. В чем разница между pip install splime. И pip install МойПакетПолезногоБарахла.

Давать по сети незащищенный доступ к куче рабочих питонов - звучит как надежный план создать поле хакерам для творчества

Подозреваю что сделано на старом добром RPC. Со всеми вытекающими.

Ну а ещё есть ray с похожей идеей - запустить что угодно в каком хочешь окружении.

Ну куча возни со своим пакетом и выпуском ему тегов - на практике делается 5 минут. Вы автоматизировали то, что заниемает 5 минут. Из которых 4ре на деплой.

А интересно какая куча возни ждет в части ИБ если поставить себе это чудо, у которого представляете без авторизации можно на удаленной ноде делать чего хочется.

Думаю вы просто хотели ray. И распределенные вычисления. Но Вам попался "сложный" airflow с его дагами. После прочтения статьи возникло да ощущение что ray ещё раз разработали. Мне он не особо нравится, но вещь мощная.

Про ИБ вы правы наполовину: демон слушает 127.0.0.1, аутентификации у локального API нет. Как docker.sock- забиндите на 0.0.0.0 и получите ровно то, что описали.

Но удалёнка устроена иначе: входящего порта на рабочей машине нет вообще. Демон сам подключается к серверу по приватным токенам и сам забирает задания, кнопки «выполни у себя этот код» не существует. Доступ только к доверенным машинам, исполняется только то, что вы опубликовали сами (или из доверенных источников, если, например, работаете в команде).

Про пакет соглашусь: опубликовать и правда пять минут. Но "платите" вы не за публикацию, а за потребление: каждый проект тянет его со всем деревом зависимостей и исполняет у себя. splime ставится один раз, и зависимости ноды к вызывающему не приезжают.

Ray заменить мы не пытаемся: нужны распределённые вычисления - берите Ray. У него юнит - это таска в кластере ради скорости; у нас - версионированная нода для легкого переиспользования. Кластер не нужен вообще.

Модель угроз в статье описана слишком достаточно плохо, спасибо за замечания, в дальнейшем учту!

Вот да - "каждый проект тянет его со всем деревом зависимостей и исполняет у себя".
А почему это проблема?

Если мой пакет лёгкий и чистый питон. А у Вас статья так и начинается - надоело простые полезные функции везде таскать. То дерево зависимостей - 0 зависимостей. Ну или там 2-3.
Архитектурно - я трачу такты ЦПУ, не напрягая сеть для передачи трафика между нодами, ради 2+2. То есть если у меня реально что-то лёгкое - то гонять ради этого сеть - как это там говорили раньше - зашквар, антипаттерн. Но по факту - анти профит.
Ваше решение увеличивает нагрузку на сеть на ровном месте - маленький питон пакет, делающий операцию допустим за 100 микро-секунд - требует в Вашем случае сетевой доступ (который в лучшем случае +10 милли-секунд), снижающий пропускную способность системы, ради того, чтобы кода меньше сделать на 5 строчек.
Ну это то чем Вы реально будете платить за уменьшение кода в данном случае.
Вы ещё говорили что делать в таком случае микро-сервис - это как с пушки по воробьям.
Но именно это и сделали, просто без докеров и контейнеров. То есть без обвеса в виде конфигов, ямлов и докерфайлов. А архитектурная суть не изменилась - сетевой доступ к простому пакету.

Если вы написали крутую оболочку, которая например дережирует дровами всех СУБД на свете - ну это своя библиотека (SQLAclhemy) - Ваше решение точно не подходит.
А если Вам нужен сетевой доступ к SQLAclhemy - ну это = микро-сервис.

Если ваша задача - держать и пере-использовать вычисления разных pytorch - ну есть Ray. Ну вроде тот самый случай, когда зависимостей действительно много, а верхне-уровневого это выглядит как "Вот картинка - дай текст" def get_markdown(picture).
И получается вы конкурируете только здесь с готовым решением.
Но с ним и докер конкурирует. Иногда проще всё упаковать в один контейнер за кафкой,
чем Ray притащить.

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

Вы сделали нано-сервис по факту. Это когда микросервис на столько маленький, что вместо готового блюда, например - венигрет. Он является - кубиком свеклы.
Но это не меняет сути - наличие сети между вызовами функций, по http/https (API) они там, gRPC, бинарю RPC, голому TCP/UDP. А отсюда есть золотое правило - наличие сети должно быть оправдано тем, что затраты на транспорт пакета должны быть существенно ниже чем время обработки операции. Ну там запаковать пакет, распаковать, послать, принять, распаковать, направить в нужный стек, вызвать функцию - вот эти все операционные расходы должны казаться ничтожными по сравнению с самой функцией.
Если функция 2+2 - то у Вас плохой венигрет.
Если функция КартинкуВМаркдаун - у Вас хороший венигрет.




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

Реальная функциональная единица для упаковки - не 2+2, а пайплайн или специализированная функция: препроцессинг, скоринг, разбор документов, обвязка над моделью, etc. Таких в команде накапливаются десятки, у каждой свои зависимости и своя история версий. Общее пространство означает единую кодовую базу: функция живёт в одном месте одной версией и зовётся по имени из любого проекта, вместо того чтобы расползаться пятью копиями, каждая со своим багом.

Оверхед амортизируется батчем: clean_amount(строка) - плохой винегрет; clean_amounts(10 000 строк) - уже нормальный. Сборка окружения разовая и кэшируется, а не платится на каждом вызове. А когда нода обязана выполниться там, где лежат данные, GPU или доступы - вы заплатите её и микросервисом, и Ray. Вопрос лишь в том, писать ли обвязку руками.

И небольшая поправка: локальный вызов идёт на loopback, а не в сеть, так что «+10 мс» там нет. Однако на изолированных рантаймах мы на каждый запуск поднимаем процесс или контейнер (опционально), и это дороже сокета, тут согласен - Вы правы.

Идея с локальным демоном как альтернативой между copy-paste и публичным пакетом закрывает реальную дыру: обычно между этими крайностями ничего вменяемого нет, разве что монорепо с общим воркспейсом, но там своя возня с версиями внутри. Вопрос по механике: как splime резолвит конфликт версий, если clean_amount опубликована с одной сигнатурой, а в другом проекте ее вызывают с изменившимся набором kwargs? Падает молча или кидает ошибку валидации на call? И как быть с side-effects внутри ноды (например, если функция пишет в файл или делает сетевой запрос), демон гарантирует идемпотентность вызова, или это целиком забота автора ноды? Local-first подход, при котором сервер подключается только по желанию, по-моему, правильный выбор доверия. Многие альтернативы сразу тащат обязательный аккаунт и хотят логин на старте.

Спасибо за вопрос! Про версии: publish изменившейся функции создаёт новую версию, старая не мутирует. Версионирование сейчас сделано достаточно банально и новый элемент с тем же именем просто инкрементирует предыдущий паблиш. Вызов call по имени объекта берёт latest, но версию можно прибить гвоздём: version=N.

А про несовпадение kwargs отвечу честно: проверки сигнатуры на этапе call сейчас нет - несовпадение ловит уже Python при исполнении, обычным TypeError, и это приезжает как упавший запуск (его стейт сохраняется, можно посмотреть run-show). То есть не тихо, но поздно. Валидировать до запуска- отличная идея, забираю в бэклог.

Про side-effects: идемпотентность целиком на авторе ноды, демон её не гарантирует и не притворяется. Он даёт изоляцию, а не чистоту: отдельное окружение, у docker-рантайма сеть по умолчанию выключена (--network none), файлы ходят через объявленные артефакты. Важный нюанс: resume считает ноду функцией от входов. Ноды из множества пересчёта выполнятся заново вместе со всеми сайд-эффектами, а замороженные не выполнятся вообще. Если внутри ноды запись в БД, это надо держать в голове.

За local-first спасибо - это осознанная позиция: SPLClient() без токенов в сеть не ходит вообще.

Sign up to leave a comment.

Articles