Ответ
Самой интересной задачей была оптимизация критического API эндпоинта для массовой обработки данных, время отклика которого под нагрузкой выросло с ~200 мс до более 2 секунд. Проблема была неочевидной, так как на низкой нагрузке все работало хорошо.
Что было сделано:
- Профилирование: С помощью Application Insights и профилировщика памяти выявили проблему N+1 запросов к базе данных внутри цикла обработки коллекции, а также неоптимальную сериализацию крупных JSON-объектов.
- Оптимизация доступа к данным: Переписал логику, заменив цикл с отдельными вызовами на один запрос с использованием
INи последующее маппинговое соединение (lookup join) в памяти.// Было (упрощенно): foreach (var item in items) { var details = await _dbContext.Details.FirstOrDefaultAsync(d => d.ItemId == item.Id); // ... обработка }
// Стало: var itemIds = items.Select(i => i.Id).ToList(); var allDetails = await _dbContext.Details .Where(d => itemIds.Contains(d.ItemId)) .ToDictionaryAsync(d => d.ItemId);
foreach (var item in items) { if (allDetails.TryGetValue(item.Id, out var details)) { // ... обработка } }
3. **Оптимизация сериализации:** Заменил рефлексивную сериализацию по умолчанию на сгенерированный с помощью Source Generators сериализатор System.Text.Json, что резко сократило аллокации памяти.
4. **Кэширование:** Добавил двухуровневое кэширование (in-memory + распределенный Redis) для редко меняющихся справочных данных, которые участвовали в расчетах.
**Результат:** Время отклика API сократилось до ~50 мс (в 40 раз), потребление CPU упало на 70%, а количество обращений к БД уменьшилось на порядок. Задача была интересна сочетанием глубокого анализа, применения различных техник оптимизации и измеримым, значимым результатом для бизнеса. Ответ 18+ 🔞
Да ты послушай, какая история приключилась! Сижу я значит, пью кофе, и тут прилетает алерт — наш главный API, который данные массово обрабатывает, сдулся конкретно. Под нагрузкой вместо положенных двухсот миллисекунд стал отвечать по две секунды, а то и больше. А на тестах-то всё летало, сука! Ну, думаю, ёпта, щас будем разбираться, где тут собака зарыта.
Что я, мудак, сделал:
-
Профилирование, мать его. Включил все датчики: Application Insights, профилировщик памяти — ну, всю эту муть. И выясняется пиздец: во-первых, там внутри цикла на каждый чих летит отдельный запрос в базу, классический N+1, ебать его в сраку. А во-вторых, здоровенные JSONы сериализуются так, будто их рефлексия через жопу гоняет, аллокаций — овердохуища.
-
Базу данных перестал ебать. Ну реально, что за дичь — в цикле стучаться за каждой записью. Вынес все IDшки, одним махом получил все нужные данные и склеил их в памяти. Смотри, как красота получилась:
// Раньше было (просто пиздец):
foreach (var item in items)
{
var details = await _dbContext.Details.FirstOrDefaultAsync(d => d.ItemId == item.Id);
// ... тут ещё какая-то хуйня
}
// А стало (нормально):
var itemIds = items.Select(i => i.Id).ToList();
var allDetails = await _dbContext.Details
.Where(d => itemIds.Contains(d.ItemId))
.ToDictionaryAsync(d => d.ItemId); // Всё, один запрос, и всё под рукой
foreach (var item in items)
{
if (allDetails.TryGetValue(item.Id, out var details))
{
// ... и работаем спокойно
}
}
-
JSON перестал порождать мусор. Выкинул нахуй стандартную сериализацию, которая как черепаха, и подключил Source Generators из System.Text.Json. Это ж, блядь, магия — код сериализатора на этапе компиляции генерится, никакой рефлексии, скорость — огонь, память почти не жрёт.
-
Кэш, куда ж без него. Накинул двухуровневый кэш: быстрый in-memory для локальных инстансов и Redis для распределёнки. Все эти справочники, которые почти не меняются, но в каждом запросе участвуют, — теперь оттуда летят, а не из базы каждый раз.
И что в итоге, спросишь? А в итоге API стал отвечать за ~50 мс, то есть в сорок раз быстрее, Карл! ЦПУ успокоился, нагрузка упала на 70%, а база данных вздохнула с облегчением — запросов к ней стало меньше на порядок. Задача была та ещё, но чертовски интересная: и поковыряться в глубинах пришлось, и разные подходы применить, а в конце ещё и бизнес такой довольный, что прям приятно.