Еще одна вещица, про которую хочу рассказать. Это про то что я наконец пощупал на этом проекте монгу.
Надеюсь получится донести эту мысль, но мне она кажется крутой в плане того, как это можно встроить в свой вижен по архитектуре систем.
Представьте себе такой домен, в котором:
1️⃣ Логика и хранимые данные часто меняются – добавляются новые таблицы, в самих структурах могут добавляться новые поля.
2️⃣ К примеру это какие-то временные акции, конкурсы итп.
3️⃣ Хранить такие данные нужно ограниченное время (год-два максимум, но может и больше).
А еще данные по каждому пользователю слабосвязанные и вам не нужны селекты через несколько таблиц
В общем, если это делать на реляционных базах данных то это будет чуть неудобно (ну может даже пиздец как неудобно), ибо миграции, и вот это все. А вот на монге это оказывается можно довольно удобно замутить!
У нас это реализовано вот как:
public class OperationalData<T>
{
string ShardKey {get; set;}
T Payload {get; set;}
int LifeTime {get; set;}
}
public class PromotionStatus
{
string UserName {get; set;}
Promotion CurrentPromotionType {get; set;}
DateTime NextPromotion {get; set;}
}
public class Discount
{
Guid Id {get; set;}
DateTime AvailableFrom {get; set;}
DateTime AvailableTo {get; set;}
}
И вот в такой приятной хранилке ты можешь делать вот такие штуки:
var userPromotionStatus = db.Collection.Find(x => x.ShardKey == $"PromotionStatus_{UserId}").ToPromotionStatus();
var userDiscount = db.Collection.Find(x => x.ShardKey == $"Discount_{UserId}").ToDiscount();
Я это очень удобно заюзал. Правда мне вот это как раз стрельнуло в ногу чутка.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3❤🔥2👍1
Посмотрел на эту картинку, и такой: “Да ну!” А потом присмотрелся и осознал, что все-таки, “Ну да”.
Там прям реально все все кейсы прописаны и задачи комьюнити и встречи с другой команды.
Я пол часа пыхтел, пытаясь выдумать кейс, который бы не попадал в эту табличку и потерпел глубочайший крахЪ
Завтра точно буду юзать🙏
Там прям реально все все кейсы прописаны и задачи комьюнити и встречи с другой команды.
Я пол часа пыхтел, пытаясь выдумать кейс, который бы не попадал в эту табличку и потерпел глубочайший крахЪ
Завтра точно буду юзать
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1
Не так давно прочитал книгу ✅ Высокопроизводительный код на .Net. И теперь осознал, что память не бесконечная и тратится очень быстро с каждым вызовом new. В мученических своих поисках о том как этого всего избегать, наткнулся на прикольный класс ObjectPool.
Идея простая:
1️⃣ Создаете сколько-то пустых объектов в памяти заранее
2️⃣ Потом достаете их, заполняетете нужными данными, юзаете это дельце как не в себя
3️⃣ А в конце просто сбрасываете состояние этого объекта к нулю и потом переиспользуете, если нужно.
Долго думал, как же эту крутотень использовать. И что-то никак не мог найти практического применения. А потом чет поскреб по сусекам и осознал, что в кэшах же!
Для примера. Есть самый простой в реализации кэш со словарем в качестве хранилища:
public class ExpensiveObject
{
public int Data { get; set; }
public ExpensiveObject()
{
Data = new Random().Next();
}
}
public class SimpleCache
{
private readonly Dictionary<string, ExpensiveObject> _cache = new Dictionary<string, ExpensiveObject>();
public ExpensiveObject Get(string key)
{
if (!_cache.TryGetValue(key, out var obj))
{
// Если объекта по ключу нет, создаём новый
obj = new ExpensiveObject();
_cache[key] = obj;
}
return obj;
}
}
В чем минус этого кэша. Каждый раз, когда вы добавляете в него новое значение, вы кладете объект в кучу. В этой реализации нет очистки кэша, и это, конечно, может плохо кончиться. Но, даже если добавить очистку, вы все равно будете через какое-то время триггерить сборку мусора, что тоже не очень хорошо.
Поэтому на первый план выходит идея – а что если заранее создать кучу пустых объектов и по очереди их заполнять нужными данными на время использования. Делаем то же самое на ObjectPool.
Сначала добавим немного сервисных штук:
using Microsoft.Extensions.ObjectPool;
public class ExpensiveObject
{
public int Data { get; set; }
public ExpensiveObject()
{
Data = new Random().Next();
}
// Добавляем метод сброса состояния для нашего класса
public void Reset()
{
Data = 0;
}
}
// Создаем политику работы с БАССЕЙНОМ для нашего объекта
public class ExpensiveObjectPooledPolicy : PooledObjectPolicy<ExpensiveObject>
{
// то бишь говорим, как создать
public override ExpensiveObject Create() => new ExpensiveObject();
// и как очистить состояние, после того как поюзали
public override bool Return(ExpensiveObject obj)
{
obj.Reset();
return true;
}
}
В этой части главное – не забыть модифицировать метод Return(ExpensiveObject obj), если добавили новое поле.
Если все сделали, то потом используем в пулле
using Microsoft.Extensions.ObjectPool;
public class OptimizedCache
{
private readonly ObjectPool<ExpensiveObject> _objectPool;
private readonly Dictionary<string, ExpensiveObject> _cache = new Dictionary<string, ExpensiveObject>();
public OptimizedCache(int maximumRetained = 20)
{
var policy = new ExpensiveObjectPooledPolicy();
_objectPool = new DefaultObjectPool<ExpensiveObject>(policy, maximumRetained);
}
public ExpensiveObject Get(string key)
{
if (!_cache.TryGetValue(key, out var obj))
{
obj = _objectPool.Get();
_cache[key] = obj;
}
return obj;
}
// Когда поюзали объект, можем его удалить.
public void Remove(string key)
{
if (_cache.TryGetValue(key, out var obj))
{
_cache.Remove(key);
_objectPool.Return(obj);
}
}
}
Если что, это непотокобезопасная реализация, разумеется, и тут разумеется не поможет конкаррент дикшенари и вот это все. То есть нужно будет чутка заморочиться с синхронизацией. Если кто-то юзает объект прямо сейчас, то нужно это отслеживать и дать чуваку доделать делишки и дождаться пока объект вернется в пул в первозданном виде.
Придется чутка подзаморочитсья. Но идею думаю вы уловили.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤🔥1🔥1
🅰️ Итого: Короче, классный ObjectPool юзайте, экономьте память, кайфуйте. И он вроде не особо сложный для понимания, что очень круто – это не указатели, не какие-то там интринсики и прочее байтоёбство. Простая и понятная штука!
🔥3🤝1
🎱 ObjectPool бенчмарки 1
В комментах Валера Никитин @nvikv попросил бенчмарки, я сегодня посидел, помозговал и сделал вот такую штуку:
1️⃣ Делаем запрос
2️⃣ Кладем в кэш значение
3️⃣ “Пользуемся” этим значением
4️⃣ Удаляем из кэша
Сделал два эндпоинта. Один с простым кэшем, один с пулловым, побомбил💣 это все через k6 примерно 4500 запросов за 20 минуты и вот ожидаемый результат.
В простом кэше managed memory доходила до 84 Мб, потом приходил GC и все собирал. Получилось три сборки мусора за 4500 тысячи запросов
В комментах Валера Никитин @nvikv попросил бенчмарки, я сегодня посидел, помозговал и сделал вот такую штуку:
1️⃣ Делаем запрос
2️⃣ Кладем в кэш значение
3️⃣ “Пользуемся” этим значением
4️⃣ Удаляем из кэша
Сделал два эндпоинта. Один с простым кэшем, один с пулловым, побомбил
В простом кэше managed memory доходила до 84 Мб, потом приходил GC и все собирал. Получилось три сборки мусора за 4500 тысячи запросов
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1🤝1
ObjectPool бенчмарки 2
Во втором случае, ну набежало 11 Мб и это по сути ASP.NET оверхед, который накопился бы даже если бы мы дергали пустой эндпоинт. В общем, память не раздувалась и это ожидаемый результат.
То есть 0️⃣ сборок мусора за 4500 запросов против 3️⃣!
Пока писал бенчмарк, понял, что так еще круто кэшировать большие объекты, которые тупо затратно создавать каждый раз. Создал заранее много, а потом достаешь уже готовые по необходимости. То есть еще и на создании чуток экономишь.
Вот тут код. Можете посмотреть, как все устроено. Я там для большей показаетльности сделал ExpensiveObject более экспансивным, что он стал весить 80 Кб, и заюзал ConcurrentDictionary чтобы можно было кучу запросов параллельно делать. Но суть примерно та же что и в примере выше.
P.S.: Спасибо большое Валере за идею! Валера получает от меня сотку лайкосов💯 👍
Во втором случае, ну набежало 11 Мб и это по сути ASP.NET оверхед, который накопился бы даже если бы мы дергали пустой эндпоинт. В общем, память не раздувалась и это ожидаемый результат.
То есть 0️⃣ сборок мусора за 4500 запросов против 3️⃣!
Пока писал бенчмарк, понял, что так еще круто кэшировать большие объекты, которые тупо затратно создавать каждый раз. Создал заранее много, а потом достаешь уже готовые по необходимости. То есть еще и на создании чуток экономишь.
Вот тут код. Можете посмотреть, как все устроено. Я там для большей показаетльности сделал ExpensiveObject более экспансивным, что он стал весить 80 Кб, и заюзал ConcurrentDictionary чтобы можно было кучу запросов параллельно делать. Но суть примерно та же что и в примере выше.
P.S.: Спасибо большое Валере за идею! Валера получает от меня сотку лайкосов
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤🔥2❤2💯1
Сегодня 14 февраля. И это традиционный день с повышенной нагрузкой в Додо. Для бизнеса это значит много потраченных клиентами деняк
Короче, это славный денек, чтобы добить историю про Геншин, ибо она тоже связана с нагрузкой.
Могу вам однозначно сказать, что разрабатывать коллабу для анимешников очень страшно. Я уже писал, что боюсь школьников. Так вот анимешников я боюсь еще больше, чем школьников. А школьников-анимешников боюсь до усрачки.
И не потому, что ощущаю в них социальную неустроенность. А скорее потому, что они дохуя умные и пронырливые. А еще они сплоченые, когда надо.
Вместе с пиццей шел предмет, который представлял для фанатов особую ценность – кусок прозрачного плексиглаза с впаенной в него картинкой персонажа (на пояснительном дикпике выше
Систему в целом готовили к нагрузкам. Мы вообще много времени на это тратим. Очень много… Но как оказалось недостаточно чтобы сдержать то что шло дальше.
Днем закономерно нам положили систему попытками заказать коллабную пиццу.
Было такое ощущение, как будто ты на экзамен идешь, и перед тобой не сдал самый умный чел на потоке, а ты следующий.
И вот подходит наше время и… За четыре недели ничего такого не случилось и все прошло довольно гладко. Это были нервные 4 недели в которые ничего не произошло.
Please open Telegram to view this post
VIEW IN TELEGRAM
😁6❤🔥2🦄2❤1
Круто, что у нас чуваки делают смело такие штуки. С кем еще вы можете попердеть на макеты вместе? А вот у нас с дизайнерами можете!
P.S: Мне только мальца странной показалась валентинка, про “Глядя на тебя, мой прод падает”. Даешь стоящий как кол прод! Особенно сегодня это актуально.
Please open Telegram to view this post
VIEW IN TELEGRAM
😁2
Короче, решил попробовать в этом году прокачаться, как спикер на девфоруме и в поисках актуальных тем спросил наших техлидов, в чем были основные затыки у ребят и девчат на Level Assessment. И одна из таких штук это Bulkhead паттерн.
Это не паттерн в контексте паттернов разработки, а паттерн в контексте устойчивости к сбоям.
Первое, что меня поразило в этом паттерне, так это то, что bulkhead не переводится, как балка головы. А переводится как “переборка”.
А второе, что это паттерн, который можно применить на двух уровнях:
1️⃣ На архитектурном
2️⃣ И на уровне кода
Это значит, что вам нужно располагать зависимость между сервисами по возможности изолированно:
❌ Типа не так:
Service A -> Service B -> Database
✅ А так:
Service A -> Database
Service B -> Database
Это значит, что вам нужно делать так, чтобы взбесившийся участок кода не выжрал все ресурсы остального приложения. К примеру у вас есть серия запросов к какому-то сервису. И вы хотите их распараллелить.
❌ Если вы сделаете вот так, если вас завалит запросами, то вы исчерпаете пул соединений, если придет куча запросов.
public async Task SendRequestsWithoutBulkhead(HttpClient httpClient, List<HttpRequestMessage> requests)
{
var tasks = new List<Task<HttpResponseMessage>>();
foreach (var request in requests)
{
// Все запросы отправляются параллельно без ограничений
tasks.Add(httpClient.SendAsync(request));
}
await Task.WhenAll(tasks);
}
✅ Вместо этого вы можете ограничить ресурсы с помощью библиотеки Polly:
// Создаём Bulkhead policy с ограничением 10 одновременных запросов и очередью до 20 запросов.
private readonly AsyncBulkheadPolicy<HttpResponseMessage> _bulkheadPolicy = Policy.BulkheadAsync<HttpResponseMessage>(
maxParallelization: 10,
maxQueuingActions: 20,
onBulkheadRejectedAsync: context =>
{
Console.WriteLine("Запрос отклонён из-за перегрузки Bulkhead.");
return Task.CompletedTask;
});
public async Task SendRequestsWithBulkhead(HttpClient httpClient, List<HttpRequestMessage> requests)
{
var tasks = new List<Task<HttpResponseMessage>>();
foreach (var request in requests)
{
// Каждый запрос выполняется через Bulkhead policy,
// что гарантирует, что одновременно будет не более 10 запросов,
// а остальные будут ждать в очереди до 20.
tasks.Add(_bulkheadPolicy.ExecuteAsync(() => httpClient.SendAsync(request)));
}
await Task.WhenAll(tasks);
}
Благодаря Polly это выглядит довльно лайтово. И это круто! Потому что самому писать какую-то ограничивалку может быть писать лень, а тут уже все готово.
🅰️ Итого: Юзайте bulkhead! Изично работает в коде, бахнули Polly и все! Вы спасены на случай, если что-то разложится на молекулы
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍3❤2🙏1
Тут в канале yet another dev вышел пост про FuentAssertoins и на что его заменить.
В итоге в комментах предложили AwesomeAssertions. И я попробовал это дело применить в своем пет-проектике.
И…
Оно работает! Так как это форк, то там неймспейсы те же. То есть просто меняем название пэкаджа и все
🅰️ Итого: Так что если искали, на что заменить FluentAssertions, то муки выбора можно отбросить – меняйте на AwesomeAssertions.
P.S.: И там это, если вдруг пройдете по ссылочке в репозиторий, то закиньте там звезду ребятам, чисто по-человечески
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
yet another dev
FluentAssertions стала платной
Коллеги, если вы вдруг пропустили новость, то сообщаю. Библиотека для тестирования FluentAssertions стала платной около месяца назад. Теперь стандартная лицензия для 1 разработчика стоит $129.95 в год, а для небольших компаний…
Коллеги, если вы вдруг пропустили новость, то сообщаю. Библиотека для тестирования FluentAssertions стала платной около месяца назад. Теперь стандартная лицензия для 1 разработчика стоит $129.95 в год, а для небольших компаний…
👍3❤2💯2
📚Сказ о том, как мы с Саньком OpenAI в наш таск-трекер пытались интегрировать.
В прошлый четверг с прекрасным @sagos95 сделали доклад на девфоруме про то, как мы попытались прикрутить LLMку к поиску по нашему корпоративному таск-трекеру Kaiten.
Саму попытку совершали в октябре прошлого года, так что какие-то вещи уже устарели (к примеру дипсика тогда не было), но на суть доклада это не влияет.
🎞 Смотреть можно туть
Коротко текстом, о чем видос
1️⃣ Хотели научиться искать дубликаты багов в таск-трекере по абстракному описанию типа "Э-э-эм, ну мы тут сделали кнопку с чаевыми вроде и вроде были какие-то баги с ней. Можешь найти?"
2️⃣ Попробовали просто пихнуть 100 задачек в /completions эндпоинт OpenAI – получилось хорошо
3️⃣ На 1000 задачек окна контекста уже не хватило
4️⃣ Дальше идет рассказ про то, как мы пытались отфильтровать из >1000 карточек только те, что относятся к задаче
5️⃣ @sagos95 рассказывает про векторизацию и векторное сходство, а также про то, что такое Retrieval Augmented Search и как он в целом работает.
6️⃣ Рассказываем про проблему антонимов и как она делает неподходящие по смыслу запроса карточки проблемой такого поиска.
7️⃣ Показываем код и пайплайн обработки данных
🎱 Показываю просто 🩼🩼🩼 МЕГАКОСТЫЛЬ 🩼🩼🩼 для поиска
9️⃣ Делаем выводы.
Ну и коротко поделюсь тут своими эмоциями от работы над поиском, усиленным AI-шкой😱
Ищет эта штука данные по не супер-стабильно. На малой выборке все работает более менее, но чем больше даете на вход, тем хуже становится разброс. Мы все это делали на 4o модельке, но не думаю, что дело в ее развитости, тут вопрос скорее всего в принципах работы этой штуки
Поэтому LLM надо прям затачивать под задачи. Чем более узкая задача, тем лучше будет выход.
То есть если просто загрузить карточки и начать задавать по ним вопросы, то пользователю будет не очень удобно – ему придется все время писать “выдай в таком-то формате, не пиши то-то, а пиши вот так-то”.
Дебажить эту штуку пиздец как сложно – вот она тебе выдала все правильно. Снова что-то спросил, даже то же самое и она просто ничего не выдала. Как? Почему? И тестов так-то не напишешь. По крайней мере я плохо как-то представляю такие тесты.
P.S.: Но кстати, мне кажется, что само по себе тестирование AI штук – интересный топик. Я бы поглядел какие-то доклады про енто дело.
P.P.S.: Кстати, если тоже делали RAG поиск, то поделитесь в комментах, что у вас получалось с этим делом, будет интересно почитать.
В прошлый четверг с прекрасным @sagos95 сделали доклад на девфоруме про то, как мы попытались прикрутить LLMку к поиску по нашему корпоративному таск-трекеру Kaiten.
Саму попытку совершали в октябре прошлого года, так что какие-то вещи уже устарели (к примеру дипсика тогда не было), но на суть доклада это не влияет.
Коротко текстом, о чем видос
1️⃣ Хотели научиться искать дубликаты багов в таск-трекере по абстракному описанию типа "Э-э-эм, ну мы тут сделали кнопку с чаевыми вроде и вроде были какие-то баги с ней. Можешь найти?"
2️⃣ Попробовали просто пихнуть 100 задачек в /completions эндпоинт OpenAI – получилось хорошо
3️⃣ На 1000 задачек окна контекста уже не хватило
4️⃣ Дальше идет рассказ про то, как мы пытались отфильтровать из >1000 карточек только те, что относятся к задаче
5️⃣ @sagos95 рассказывает про векторизацию и векторное сходство, а также про то, что такое Retrieval Augmented Search и как он в целом работает.
6️⃣ Рассказываем про проблему антонимов и как она делает неподходящие по смыслу запроса карточки проблемой такого поиска.
7️⃣ Показываем код и пайплайн обработки данных
🎱 Показываю просто 🩼🩼🩼 МЕГАКОСТЫЛЬ 🩼🩼🩼 для поиска
9️⃣ Делаем выводы.
Ну и коротко поделюсь тут своими эмоциями от работы над поиском, усиленным AI-шкой
Ищет эта штука данные по не супер-стабильно. На малой выборке все работает более менее, но чем больше даете на вход, тем хуже становится разброс. Мы все это делали на 4o модельке, но не думаю, что дело в ее развитости, тут вопрос скорее всего в принципах работы этой штуки
Поэтому LLM надо прям затачивать под задачи. Чем более узкая задача, тем лучше будет выход.
То есть если просто загрузить карточки и начать задавать по ним вопросы, то пользователю будет не очень удобно – ему придется все время писать “выдай в таком-то формате, не пиши то-то, а пиши вот так-то”.
Дебажить эту штуку пиздец как сложно – вот она тебе выдала все правильно. Снова что-то спросил, даже то же самое и она просто ничего не выдала. Как? Почему? И тестов так-то не напишешь. По крайней мере я плохо как-то представляю такие тесты.
P.S.: Но кстати, мне кажется, что само по себе тестирование AI штук – интересный топик. Я бы поглядел какие-то доклады про енто дело.
P.P.S.: Кстати, если тоже делали RAG поиск, то поделитесь в комментах, что у вас получалось с этим делом, будет интересно почитать.
Please open Telegram to view this post
VIEW IN TELEGRAM
YouTube
DevForum 20.02. Дмитрий Афонченко/Александр Журавлев "Как мы AI поиск по Kaiten пытались сделать."
Привет! На этой неделе Дмитрий Афонченко и Александр Журавлев, backend разработчики, поделятся своим опытом создания AI-поиска по Kaiten за пару недель.
Ребята расскажут, какую задачу хотели решить и как сделали первый POC. Поделятся проблемами, возможными…
Ребята расскажут, какую задачу хотели решить и как сделали первый POC. Поделятся проблемами, возможными…
🔥6❤🔥2👏1