Microservices Thoughts
7.79K subscribers
33 photos
65 links
Вопросы и авторские статьи по микросервисам, архитектуре, БД

Сотрудничество: t.me/qsqnk
Download Telegram
Предновогодний пост

Хочу поделиться одной из наиболее релевантных тем для меня за этот год — как не загнуться в новой роли

На работе за этот год я побывал в 4х ролях (или почему посты выходят раз в месяц)

1. Лид одной команды бэкендеров

2. Лид всего бэкенда (несколько команд)

3. Лид всего бэкенда + лид vteam по внедрению ml в клиентскую поддержку

*происходит реорг, который функциональные структуры превращает в кросс-функциональные команды + гильдии*

4. Лид гильдии бэкенда + лид vteam + зам своего рук-ля

---

Каждый такой переход — это своего рода шажок в неизвестность, который сопровождается
- страхом, что не получится
- непониманием, а с чего начать

Хорошая новость — с этим можно что-то делать

Страх, что не получится

Но что такое "не получится"? Это значит кто-то тобой недоволен => ты нарушил чьи-то ожидания => а были какие-то ожидания?

Были. И очень важно в явном виде сформулировать взаимные ожидания со всеми ключевыми стейколдерами, с которыми тебе надо будет взаимодействовать

По крайней мере, это даст понимание, хорошо или плохо ты справляешься

Непонимание, а с чего начать

Из предыдущих разговоров у тебя скорее всего появится список актуальных проблем в твоей текущей зоне ответственности

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

---

Если подытожить в трех пунктах, то алгоритм выглядит так:
1. Сформировать ожидания с ключевыми стейкхолдерами
2. Набрать список текущих проблем
3. Показать какие-то quick wins

После этого дальше двигаться будет сильно проще

---

И в заключение хочу вас поздравить с наступающим Новым годом! Пусть вас окружают близкие по духу люди, поражения не ломают, а победы вдохновляют)
2👍100🔥32🤔6💅1
Yet another пост про то, как победить неопределенность

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

Мне помогает такой способ:
1. Взять пункт, который точно придется делать
2. Подумать, что должно быть до него
3. Подумать, что должно быть после него

Пример: нужно сделать гайдлайны по написанию тестов в нашей системе

Что точно нужно сделать?
- написать пример теста

Что мне нужно для выполнения этого пункта?
- определиться, какого типа будет этот тест (unit / integration / ...)
- определиться, на что писать этот тест
- определиться, как будет выглядить этот тест

Что надо сделать после?
- добавить этот пример в доку
- презентовать команде

И из этого вырисовывается вполне понятный список дел:
1. определиться, какого типа хотим тесты
2. определиться, на что пишем тесты
3. определиться с кодстайлом
4. написать пример теста
5. написать доку
6. презентовать команде

И таким образом из размытой непонятной задачи сформировали вполне понятный план. Пользуйтесь!
2👍74🔥17
Про Стратоплан и менеджмент pt.3

На новогодних закрываю долги по курсу. Очередные 4 занятия:
• Основы финансового менеджмента
• Построение финмоделей
• Бюджетирование
• Управление ресурсами

Первые два занятия — введение в финансы: как двигаются деньги в компании, основные отчеты (PnL, Cash Flow, Баланс), коэффициенты рентабельности и т.п.

Еще из интересного: в эксельке посмотрели на реальные финмодели из прошлой практики преподавателя и попробовали оценить целесообразность в них инвестировать с помощью NPV, IRR, PP, DPP

Как считать NPV (Net Present Value): берем прогноз денежных потоков (поступления и выплаты), берем некоторую ставку дисконтирования r — ставку, позволяющая перевести будущие денежные потоки в текущую стоимость, может быть %-ом инфляции, ставкой кредитования, WACC, ...

И далее считаем сумму денежных потоков (CF) с учетом ставки дисконтирования (r) и из нее вычитаем начальные инвестиции

NPV = Σ (CFₜ / (1 + r)ᵗ) − CF₀

Если NPV > 0 за некоторый период, то с учетом данной ставки дисконтирования проект окупится

IRR (Internal Rate of Return) — связанная вещь, где в формуле выше ставка дисконтирования берется за переменную, и считается при какой ставке дисконтирования проект сможет окупить инвестиции

PP (Payback Period), DPP (Discounted Payback Period) — всё та же формула. Только уже считаем за какой срок окупятся инвестиции в проект без учета/с учетом ставки дисконтирования

Это всё конечно занимательно, но хороший вопрос — где это прямо сейчас применять. Скорее это пока из серии "через месяц забуду и вернусь к ним, когда понадобится"



Третье и четвертое занятия в этом смысле более прикладные

Третье — про процесс работы с бюджетами: финансовый и операционный бюджеты; как планировать, защищать, контролировать и т.д. Отдельно порадовали кулстори из жизни про то, как можно защитить бюджет, если начальство не согласно (иногда не меняя общую сумму)

И последнее в этом блоке занятие — про планирование ресурсов. Тут мне понравился алгоритм, так что даже поделюсь:

Задача — запланировать проекты на квартал/полугодие/...

1. Делаем карту компетенций по сотрудникам команды. Если не требуется чего-то особенного, зачастую компетенции будут уровня back, front, qa, аналитика и т.п.

2. Делаем табличку "team capacity" — сколько ресурсов определенной компетенции есть в рамках команды. И здесь хороший вопрос — в каких единицах измерения оценивать: можно в обычных человекочасах, это простой способ, но одновременно крайне неточный. Можно в "идеальных человекочасах" — это когда мы берем условного разработчика, откидываем от него ~10% времени на поддержку, ~20% времени на техдолг, ~10% на "срочные влеты", ~15% на встречи, и получаем скудные ~35-40% времени на написание новых фичей — именно это называется идеальными человекочасами, которые у разработчика будут на делание проекта

3. Делаем табличку "спрос" — сколько ресурсов определенной компетенции нужно для завершения проектов X, Y, Z

4. И сводя "team capacity" и "спрос" мы можем получить, сколько % ресурсов определенной компетенции у нас есть для закрытия всех проектов. Например back - 52%, front - 56%, qa - 39%. Именно это нам позволяет понять узкое место команды

5. И далее наша цель — максимально нагрузить узкое место, поскольку оно определяет мощность команды, выбрав при этом проекты с максимальными эффектами

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

Пишите мнение в комментариях, задавайте вопросы
👍23🔥17
Нагруженные счетчики на postgres

Недавно смотрел доклад и наткнулся на интересный хак

Есть классическая таблица со счетчиками


create table counters (
id int primary key,
cnt bigint
);


И обновлениями по id


update counters set cnt = cnt + 1 where id = ...


Если нагрузка небольшая или размазана по счетчикам с разными id, все будет ок. При этом если начнется массовый поток апдейтов на небольшой набор каунтеров, будут интересные спецэффекты:

• как известно, update физически не обновляет строку, а создает ее новую версию из-за MVCC

• autovacuum, который должен чистить "мертвые" версии строк, может этого не делать по куче причин

• и постгрес, чтобы сделать очередной update по id, начинает вычитывать кучу версий строк в поисках "живой" версии (похожая ситуация описывалась в этом посте)

Как следствие — серьезная деградация производительности



Решение 1 — не заставлять делать постгрес, что он не должен делать

Решение 2 — подсказать постгресу, какую именно версию строки нужно прочитать

Добавляем к счетчикам колонку last_updated — таймстемп последнего обновления


create table counters (
id int primary key,
cnt bigint,
last_updated timestamp
);

create index g1 on counters(id, last_updated);


Имея такое, мы можем попросить постгрес сделать апдейт по конкретному ctid (физическому местоположению строки), при этом внутренний селект будет выполняться быстро даже в случае серьезного блоата


update counters
set cnt = cnt + 1, last_updated = now()
where ctid = (
select ctid
from counters
where id = ...
order by last_updated desc
limit 1
);


Важный момент — если между селектом и апдейтом ctid "последней" версии строки поменялся (кто-то конкуретно обновил счетчик), то наш апдейт ничего не поменяет. Поэтому важно этот запрос обернуть в блокировку по id

Получается как-то так


select pg_advisory_lock(...)

update counters
set cnt = cnt + 1, last_updated = now()
where ctid = (
select ctid
from counters
where id = ...
order by last_updated desc
limit 1
);

select pg_advisory_unlock(...)


Это может помочь вам обезопасить базу, если вдруг у вас есть супер нагруженные счетчики и нет возможности съехать с постгреса

А вообще рекомендую посмотреть доклад полностью, помимо проблемы выше, там рассказано еще много чего интересного. Тайминг конкретно по кейсу выше - четвертая минута
1👍60🔥20💅1
Всегда было интересно, как AI Overview в гугле работает так быстро, и тут наткнулся на статью https://research.google/blog/looking-back-at-speculative-decoding/

Оказалось, что они используют технику speculative decoding

В обычном сетапе тяжелая llm генерирует токен за токеном, что может быть слишком долго/дорого. Однако, некоторые токены предсказать довольно просто:
• частые слова вроде the, is, of, and, ...
• цифры / имена, которые уже были в контексте
• повторяющиеся связки слов

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

Вместо:

large model:
context → T1
context+T1 → T2
context+T1+T2 → T3
...


Будем делать:

small model:
context → T1
context+T1 → T2
context+T1+T2 → T3

large model:
проверяет context+T1+T2+T3
ок → оставляем
не ок → large генерит сама


И если small и large модели работают "похожим образом", то это может дать серьезный буст в скорости генерации, так как мы будем часто оставлять те токены, которые предложила легковесная модель и не будем тратить ресурсы на генерацию с помощью тяжелой модели

Btw похожая техника используется в Cursor https://cursor.com/blog/instant-apply
1🔥40👍22💅2
Несколько способов дождаться результата асинхронной задачи

Сетап:
1. Клиент отправляет какой-то запрос
2. Джоба ставится в очередь задач
3. Клиент получает jobId
4. Клиент хочет дождаться результата

Поллинг

Самый простой с точки зрения бэкенда способ, нужна всего лишь апишка, которая по jobId вернет актуальный статус

И клиент в эту апишку долбится с некоторой периодичностью


client -> server: jobId = 123
client <- server: status = running

...

client -> server: jobId = 123
client <- server: status = running

...

client -> server: jobId = 123
client <- server: status = done


Лонг поллинг

Более сложный, но более удобный для клиента вариант: клиент делает один запрос, сервер ждёт, пока задача завершится (или истечет таймаут), и возвращает результат


client -> server: jobId = 123
...
client <- server: status = done


Как такое реализовать?

Идея 1: ожидание в потоке обработки


fun await(id: JobId): JobResult {
while (true) {
val result = jobService.get(id)
if (result.status != RUNNING) {
return result
}
Thread.sleep(1000)
}
}


Сделать просто, но есть несколько очевидных минусов:

• Блочится поток обработки. В классической thread-per-request модели это быстро приведет к тому, что все request-threads будут заняты ожиданием. Для легковесных го(-ко)рутин проблема сильно менее критична:)

• Можем замучать базу кучей точечных запросов

Идея 2: асинхронное ожидание

Давайте попробуем решить сразу обе проблемы — не занимать поток обработки, и не мучать базу кучей запросов. Сделать это можно так:

1. На уровне контроллера отдаем фьючу и сразу освобождаем поток


fun await(id: JobId): DeferredResult<JobResult> {
val dr = DeferredResult<JobResult>()
val future = jobWaitService.register(id)

future.whenComplete { result, _ ->
dr.setResult(result)
}

return dr
}


Соединение остаётся открытым, но поток обработки запроса освобождается сразу

2. Появляется отдельный компонент, который
• позволяет зарегистрировать ожидание задачи
• держит in-memory структуру вида Map<JobId, Future> задач, которых клиенты ожидают
• батчево поллит базу по набору JobId
• по готовности — завершает все связанные фьючи

Таким образом, ожидание не держит request-thread и в базу идет контролируемый батч-запрос раз в какой-то промежуток времени



Альтернативы: вебсокеты, SSE, коллбэки

Пишите в комментах, что из этого используете, и какие еще знаете варианты реализации
🔥25👍8💅2
Про Стратоплан и менеджмент pt.4

Таки осилил основную часть курса "Руководитель отдела", были три занятия про
• Целеполагание и реализацию стратегии
• Найм и развитие ключевых сотрудников
• Системный people-management

Первое занятие

Достаточное лайтовое. Ключевая мысль: есть perfomance goals, есть development goals
• perfomance goals обычно "SMART-уются" и влияют на ближайшее вознаграждение. Пример: увеличить метрику X на Y п.п. до ближайшего perfomance review
• development goals — наоборот: их обычно сложно измерить, и нацелены на развитие на будущее. Пример: улучшить софты

При этом development goals можно достигать по разному. Например, к улучшению софтов можно подходить разными путями: можно бить в публичные выступления, можно бить в решение конфликтов и т.п.

И то, каким путем достигается цель, это и есть стратегия — т.е. некоторый вектор приложения сил

И для декомпозиции таких целей предлагается фреймворк GOSPA (Goals, Objectives, Strategies, Plans, and Activities). Btw, по этому фреймворку бывает удобно описать какие-то личные цели, не касающиеся работы

Второе занятие

В основном дается некоторая база про найм и развитие: портрет кандидата, матрицы компетенций и т.д.

При этом запомнились несколько интересных мыслей

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

Почему это важно? Один тимлид может выжигать команду, заставлять овертаймить, и таким образом давать результат. Второй может очень мягко идти в долгосрочную мотивацию. И парадоксально — и то, и то может быть как ок, так и не ок в рамках определенной корп культуры

И очень неплохо такое вскрывать помогают проективные вопросы. Это работает так: задается какой-то общий вопрос типа "как думаешь, почему люди обычно увольняются?". И вопрос вроде бы про "всех", но человек скорее всего начнет говорить про себя, через призму своего опыта

Третье занятие

Про системный people-management. Мое любимое, так как немного сдвинуло парадигму мышления

Ключевая мысль: поведение людей — рациональный ответ на структуру системы. И если поведение не нравится, то проблема почти всегда в системе, а не в людях

Как это работает? Во всех компаниях есть вещи, которые явно не прописаны, но которые определяют, как работает система:
• кто по факту принимает решения?
• кто имеет право-вето?
• как на самом деле реагируют на ошибки?
• за что на самом деле поощряют и продвигают?

И каждый руководитель дает своим сотрудникам эти сигналы, зачастую усиливая сигналы руководителя сверху. Пример:
• CTO: проект X нужно выполнить к дд.мм.ГГГГ! Если что то пойдет не так, виноватых найдем и накажем!
• Рук отдела/TL: ошибаться опасно, нужно жестче контролировать разработчиков
• Разработчик: сделаю ровно то, что просят. Жду новую постановку задачи. Если будут делать ровно то, что скажут, то меня не накажут

В итоге из-за сигнала сверху что ошибаться нельзя, получили безынициативных разработчиков

И чтобы менять систему, очень важно уметь замечать такие сигналы. Далее в занятии даются практики, которые помогают раздебажить систему, и понять, а как ее менять. Где точка наиболее эффективного приложения сил



Как-то так! Еще осталось досмотреть несколько занятий из курса "основа" — это 4 больших воркшопа, посвященных общим руководительским навыкам, не привязанным к конкретной роли. Про это будет еще 1 или 2 поста

Пишите в комментах с чем согласны/не согласны, что показалось интересным
🔥45👍20🙏42💅1
AI ускоряет разработчиков. Почему фичи начинают шипаться дольше?

В свете последних событий с массовым использованием ИИ-ассистентов вижу интересную картину:

Разрабы начинают юзать Cursor →
Код пишется быстрее →
Фичи в проде появляются медленнее


Объяснить этот феномен может закон Литтла

Cycle Time = WIP / Throughput


• WIP — сколько фич одновременно в работе у команды
• Throughput — сколько фич полноценно завершает команда за единицу времени
• Cycle Time — сколько времени занимает полноценно сделать одну фичу

Что происходит с AI

Чтобы полноценно сделать фичу, нужна совместная работа дискавери, дизайна, бэка, фронта, QA

Cursor увеличивает локальную скорость написания кода

И получаем примерно следующую картину:

Бэк быстро сделал свою часть фичи →
Бэк берет следующую фичу →
Очередь перед фронтом и QA растет →
WIP растет


С WIP разобрались. Но что насчет Throughput? Кажется, что если Cursor ускоряет индивидуальных разработчиков, то должен ускорять и всю команду целиком

Но есть нюансы:
• Если боттлнек — не разработка, то ускорение разработки сделает только хуже для команды в целом
• Из-за роста WIP получаем безумное количество context switching

И в реальности получаем, что эффект от ускорения индивидуальных разработчиков невелируется, и Throughput либо не меняется, либо становится хуже

Если подытожить:
• WIP растет
• Throughput либо не меняется, либо ухудшается
• Согласно закону Литтла Cycle Time сильно вырастает



Какое минимальное действие можно сделать, чтобы этого избежать? На мой взгляд — это WIP-лимиты на уровне фичей

Т.е. команда одновременно работает не более чем над N фичами

И если условный бэк сделал все по своей части, он не берет (N + 1)-ую фичу в работу, а вместо этого
• либо идет исправлять техдолг
• либо в идеале идет помогать текущему узкому месту в команде, например QA

Поскольку все основные негативные эффекты с замедлением команды связаны с ростом WIP и переключениями контекста, WIP-лимиты помогут это нивелировать, и обратить ускорение разработки на пользу команды

Пишите ваше мнение в комментариях!

👍 — AI-ассистенты ускоряют работу моей команды
🔥 — замедляют
👍102🔥32🤔21💅3
Где может потеряться "exactly-once"

Представим классическую схему — два сервиса интегрированы через брокер:

producer → broker → consumer → side effect


Хочется обеспечить exactly-once обработку на всей цепочке

Что может пойти не так?

producer → broker

Продюсер записал сообщение в брокер → брокер отправляет ack → сеть сбойнула → продюсер ретраит → в брокере дубликат

Как обычно решается: зачастую общий механизм выглядит так, что каждый продюсер отправляет сообщения с монотонно-возрастающим sequenceNumber (в рамках данного продюсера), а брокер дедуплицирует по (producerId, sequenceNumber). Т.е. если от данного producerId пришло сообщение с sequenceNumber меньшим или равным, чем уже записано, то брокер его дискардит

Конкретная реализация зависит от брокера: например у кафки и нашего яндексового logbroker механизмы чуть разные

broker → consumer → side effect

Консюмер обработал сообщение → не успел закомитить offset → упал → после восстановления обрабатывает то же самое сообщение

Как обычно решается: у каждого сообщения есть уникальный идентификатор + обработка внутри консюмера идемпотентна по уникальному идентификатору сообщения

Например, консюмер может хранить идентификаторы обработанных сообщений в своей БД и выполнять дедупликацию перед применением бизнес-логики (например, через insert ... on conflict do nothing в транзакции с бизнес логикой). Если сообщение уже было обработано, повторная обработка пропускается, и применение сообщения к системе становится идемпотентным

Однако если обработка включает side effect во внешнюю систему, то для сохранения exactly-once внешняя система тоже должна поддерживать идемпотентность



Поэтому важно понимать: в большинстве случаев под капотом “exactly-once” — это at-least-once доставка + идемпотентная обработка на каждом стыке. Физически сообщения могут обрабатываться несколько раз, но система спроектирована так, что итоговое состояние меняется ровно один раз
👍71🔥18🤔4💅33
Почему загрузить разработчиков на 100% — плохая идея

Иногда в управлении командой есть очень соблазнительная мысль — давайте полностью загрузим каждого разраба, и тогда команда будет работать эффективно. На практике это часто дает обратный эффект

Занятость людей ≠ движение работы

Цель команды — протолкнуть фичу до прода. Проталкивание обычно включает в себя разработку → кодревью → тестирование → выкатку на прод

Пример, что происходит, когда все загружены на 100%. Начало спринта:

• Разработчик 1 делает фичу А
• Разработчик 2 делает фичу B
• QA тестирует предыдущую фичу C

Разработка фичи A завершена, требуется ревью. Но разработчик 2 загружен. Чтобы не просиживать, разработчик 1 берет следующую фичу D:

• разработчик 1 делает фичу А (ждет ревью)
• разработчик 1 делает фичу D
• разработчик 2 делает фичу B
• QA тестирует предыдущую фичу C

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

• разработчик 1 делает фичу А (поревьюено, ждет QA)
• разработчик 1 делает фичу D
• разработчик 2 делает фичу B (ждет ревью)
• разработчик 2 делает фичу E
• QA тестирует предыдущую фичу С

Спустя несколько дней получаем:
• растущую очередь перед кодревью
• растущую очередь перед QA
• кучу незавершенной работы

Незавершенная работа ⇒ раздутый WIP ⇒ огромный Cycle Time ⇒ фичи в проде появляются очень медленно

Ситуация выше описывается Теорией Ограничений — нет смысла усиливать ту часть производства, которая не является узким местом. Потому что это будет приводить к раздуванию очереди перед узким местом, и как следствие, повышению времени цикла

Что с AI?

Может возникнуть мысль что с AI-ассистентами проблема выше неактуальна, так как
• бэкендер может написать немного фронта
• фронт может сам поправить контракт апишки
• разрабы могут сильно помогать QA

Пункты выше действительно ослабляют проблему 100% загрузки, потому что люди становятся более "кросс-функциональными". Но тут есть важный нюанс — AI не отменяет закон очередей. Он просто делает границы между ролями чуть более размытыми

Поэтому очень важно использовать AI правильно — не генерить кучу незавершенки, а помогать разгружать узкое место. Об этом писал в одном из предыдущих постов



Пишите в комментах, если сталкивались с желанием загрузить всех на 100%, и что из этого вышло:)

P.S.: те 10% девушек, которые читают этот канал, с праздником вас!
👍49💅18🔥9🙏11
random thought

Привычные аргументы насчет гексагоналки/clean arch выглядят так:
+: высокая тестируемость
+: понятная структура проекта
-: много бойлерплейта
-: много абстракций
-: высокий порог входа

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

При этом для агентов не проблема
• написать бойлерплейт
• осознать абстракции

мэтч?
👍67🤔9💅4😁2
Про Стратоплан и менеджмент (итог)

Я уже как ~месяц назад закончил обучение в Стратоплане на руководителя отдела https://stratoplan-school.com/head/

Обучение весьма удачно совпало с новой зоной ответственностью на работе, поэтому многое удалось попробовать на практике. Если меня попросить топ выводов/мыслей, в которых убедился на собственном опыте, я бы ответил так:

• Не хочешь внезапных разочарований (в т.ч. от себя) — явно проговаривай ожидания

• Регулярное обеспечение прозрачности таки действительно рождает доверие (как и наоборот)

• Эффективность каждого конкретного человека по отдельности ≠ эффективность команды

• Если проблемы со всеми людьми одновременно, то скорее всего проблемы не с людьми)



Еще на первом занятии упоминалось, что задача рук-ля отдела — строить систему, и в целом курс действительно направлен на то, чтобы абстрагироваться от решения локальных проблем, и решать их на уровне всей системы

Поэтому я думаю, обучение точно будет полезно:
• тимлидам, у которых начала разрастаться команда, и "уследить за всеми" уже стало как-то нереально
• M2 руководителям, которые не понимают, что происходит

Если у вас только-только появилась команда, то скорее всего будет полезнее тимлидское обучение

Подробности про обучение писал в постах
раз
два
три
четыре
пять

Если интересно пообщаться лично и поспрашивать подробности, можно писать в личку, всем отвечу!
1🔥33👍24
Какой у вас Cycle Time в команде?

Время от взятия в работу до выкатки в прод типичной продуктовой задачи
Anonymous Poll
3%
< 1 дня
12%
1-3 дней
19%
4-6 дней
34%
7-14 дней
32%
14+ дней
yet another random thought

Внезапное место, где хорошо раскрылись принципы функционального программирования — LLM агенты

Агенты в большинстве своем устроены так:

• LLM — функция без сайд эффектов (string -> string)

• Все сайд-эффекты инкапсулированы в тулах

• LLM сама ничего не вызывает. А просто возвращает агенту, какие тулы нужно вызвать

А это и есть реализация паттерна
• functional core (LLM)
• imperative shell (Агент)

И в целом никто не запрещает перенять эти принципы для продакшн кода. Чистая бизнес логика — это супер-простое юнит тестирование и простота поддержки
💅28👍1832🙏1
Хотел написать пост не про эйай но получилось как обычно

Обзор на книжечку Agentic design patterns, которая оказалась скачана на телефон во время 8ми часового полета

Несмотря на многословность и не очень прикрытую рекламу гугловых инструментов (книга от инженера из гугла), она дает хорошее понимание, как из ллмки - функции, которая просто предсказывает следующий токен, набором инженерных решений получаются крутые инструменты типа claude code

Рассказываются основные паттерны, которые устоялись за последние пару лет разработки агентских систем

Базовые:
• prompt chaining — пайплайн из вызова ллмок, где результаты передаются по цепочке
• rouing — ллмка решает, куда дальше идти в воркфлоу
• parallelization — кусочки, которые можно распаралеллить, параллелим. Например, сбор данных из разных систем
• reflection — первая ллмка отвечает, вторая оценивает ответ, дает фидбек первой, первая корректирует ответ
• planning — вместо "реши сложную задачу" сначала просим ллмку сформулировать план. Далее выполняем набор более простых задачек
• reasoning techniques — chain-of-thoughts, tree-of-thoughts, ReAct и несколько других

Как достучаться до внешнего мира:
• tool use — обычный тул колинг: ллмке даются спеки тулов, а она отвечает, что и с какими аргументами нужно вызвать для продолжения работы
• mcp — простой небольшой рассказ что это, что такое mcp client, mcp server
• rag — ретривим релевантную информацию из базы знаний перед ответом

Память:
• memory management — про short term memory (на уровне одного чата) и long term memory (глобальная память)
• learning and adaptations — прикольная глава про то, как сделать так, чтобы агент автономно (или полуавтономно) обучался и не повторял тех же ошибок

Как не допустить говна:
• exception handling and recovery — просто глава-напоминание о том, что внешние системы могут лежать/агент можно не справляться с задачей, и это надо как-то уметь обрабатывать
• human in the loop — зовем человека, когда делаем рисковое действие
• guardrails — ллмке как на вход, так и на выход поступает примерно что угодно, поэтому хорошо бы добавлять явные проверки/валидации, что запрос/ответ приемлем

Системы из нескольких агентов:
• multi-agent — есть несколько агентов, заточенных под свои узкие области, которые друг другу дают задачи. Зачастую есть отдельный агент-оркестратор
• A2A — гугловый протокол взаимодействия между агентами в распределенных системах

Всякое разное около самих агентов:
• evaluation and monitoring — какие есть способы мониторить агентов и их качество

Примеров оч много (даже слишком), поэтому читается легко

7/10
🔥36😁3👍2💅2
Хочешь долгого выполнения задач — нагрузи всех подзавязку

В теории массового обслуживания есть очень простая и удобная модель M/M/1:

Есть бесконечный поток задач, один узел обслуживания и очередь перед ним


поток задач -> очередь -> узел обслуживания


Где:
• поток задач описывается Пуассоновским процессом
• время обслуживания описывается экспоненциальным распределением

Интересно вот что: если взять
• λ — скорость прихода задач
• μ — скорость обработки
• ρ = λ / μ — утилизация
• W — среднее время в системе

То получается, что
• W = 1 / (μ - λ)
(proof)

И если переписать через утилизацию:
• W = 1 / μ(1 - ρ)

То есть среднее время нахождения задачи в системе растет гиперболически относительно утилизации. Возьмем простой пример: μ = 1 задача / день

И по формуле выше получаем такое среднее время нахождения задачи в системе W:


50% utilization -> 2 дня
80% utilization -> 5 дней
90% utilization -> 10 дней
95% utilization -> 20 дней
99% utilization -> 100 дней


Такое происходит из-за того, что задачи поступают в систему неравномерно. Поэтому если обработчик загружен под 100%, то любая неравномерность приводит к скоплению очереди, и как следствие, взрыву времени ожидания

Например, при переходе 95% -> 99% утилизация выросла всего на 4п.п., при этом среднее время ожидания скакнуло с 20 дней до 100 дней

На произвольные распределения этот эффект обобщается формулой Кингмана



Какой из этого можно сделать практичный вывод? Не хочешь внезапных задержек — оставляй исполнителям задач некоторый запас капасити

Например, разработчик загружен на 100%, он сидит, работает себе, а потом ему прилетают 3 пулреквеста на ревью. И 3 задачи встанут, потому что у разработчика нет капасити на то, чтобы их поревьюить. Ну и в такой ситуации обычно начинают браться в работу новые задачи, начинает раздуваться WIP, и проявляться прочие спецэффекты, описанные в этом посте
👍59😁3🤔2
Как сделать карьерный рост чуть приятнее

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

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

Нормально это осознать помог курс от ребят из SSL, который я проходил аж в 2024 (и до сих пор считаю одним из лучших вложений)

Один из тренеров курса — Миша Ромашов, который параллельно преподает переговоры в ВШЭ и лидит одно из направлений в Сбере. Миша ведет свой тг канал, где рассказывает интересные кейсы из практики:
Про эмоции
Про чужую картину мира
Про конфликты без права сепарации

Если у вас в работе много сложных коммуникаций — рекомендую
💅9😁5👍2
Разработка фичей без достаточной экспертизы в системе зачастую превращается в набор "локально-оптимальных" решений: здесь добавили ифчик, здесь протянули новую зависимость, здесь скопипастили похожий код, здесь обошли существующую точку расширения

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

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

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

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

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

А как вы боретесь с этим явлением?
👍36💅2
Очень грубо инженеров можно классифицировать на два типа:

1. Те, кто работает с техническими проблемами проактивно:
• Увидел плавное повышение cpu usage на БД => раздебажил из-за чего, добавил нужных индексов, предотвратил инцидент
• Увидел, что новая функциональность в модуль добавляется очень странным образом => инициировал и довел до конца рефакторинг, благодаря этому крупный проект сошелся в срок
• Сделал удобные алерты, что позволило видеть проблему раньше пользователей и предотвращать инциденты

2. Те, кто работает с техническими проблемами реактивно:
• TTM фичей вырос в два раза, постоянные баги => только тогда начинаем рефакторинг
• Количество инцидентов стало совсем неприемлемым => только тогда инициируем проект по стабилизации

(да, не существует чистых типов 1 и 2, это всегда спектр, и всегда нужно уметь работать в обоих режимах)

Но в чем неприятный парадокс — признание за технический вклад в основном получают люди, работающие во втором режиме

Почему так происходит? Потому что для наблюдателей есть прозрачная логическая цепочка: что-то сломалось, конкретный человек это починил, он молодец

Если же чинить проблемы проактивно, то со стороны может показаться, мол ничего особенного, все так и должно работать — фичи делаются быстро, система работает стабильно

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

Хороший руководитель безумно ценит людей, которые самостоятельно предупреждают проблемы. И задача руководителя — помочь опрозрачить такой вклад сотрудника для остальных
👍81🔥12🤔5