📘 На гребне листа. Стриминг вместо ToList. Часть 3: когда стримить НЕ надо
Прочитав оригинальную статью, хочется встать и переписать все свои ToList на IAsyncEnumerable — и будет тебе счастье. И действительно, если накидать бенчмарк — память реально падает. Ну как бы тут сомнений не было.
Лавочная марка (она же бенчмарк):
Потестил на одних и тех же данных через k6, 8 клиентов, 12 секунд, ответ ~47 МБ на каждый запрос у всех (пояснительный дикпик 1 👆):
🍑 Пик памяти: буфер (ToListAsync) — 1483 МБ, NDJSON — 557 МБ, SSE — 208 МБ
🍑 Время до первого байта: буфер — 1.34 секунды, стримы — десятки миллисекунд
🍑 Пропускная способность — у всех примерно одинаковая
Если залимитить рост памяти то буфер вообще падает с OutOfMemoryException, пока стримы спокойно отдают свои 117 МБ. Казалось бы — всё, бежим переписывать!
Но мне в голову пришло пару моментов.
1️⃣ Медленный консюмер. Пока он по чуть-чуть вычитывает ответ, сервер держит открытым соединение с базой всё это время. Пул в 3 соединения, 5 медленных клиентов — и стрим лёг: 3 отдались, 2 отвалились (пояснительный дикпик 2 👆).
Буфер же прочитет всё залпом и отпустит. Под медленного клиента он ведёт себя приличнее. И ещё важно, при стриминге на забыть AsNoTracking и выключить retry у EF — иначе «стрим» тихо превращается обратно в буфер.
Ну а если ответы повторяют свое сожержание, то тут как будто можно обойтись простым кэшированием.
2️⃣ Cтримить всё подряд и не надо. Если задача «отдавай по 10–20 статей по мере прокрутки», то обычная пагинация поверх ToList зайдёт куда лучше: нет никакой беды загрузить одну страницу в память, зато отдаётся быстро и мелко, даже если клиент тормозит. Бери keyset — курсор по последнему id, без OFFSET, прямо по индексу (пояснительный дикпик 3 👆).
Короч, статья подаёт стриминг, как оптимизацию памяти, которую как бы можно натянуть на каждый ToList. Но по сутивсе же стриминг — это не совсем «оптимизация памяти». Это один из способов реализовать бизнес-логику, которая заодно экономит память — но только если твой кейс под него заточен. Вот там у автора как раз так и было. Там типа логи стримились.
🅰️ Итого вот что я понил:
🦄 Стриминг реально снижает нагрузку на память — отдаём по строке, весь набор в куче не держим.
🦄 Но аккуратнее с медленными читателями: они форсят удержание соединения с базой, и пул недолго выжрать.
🦄 Пагинация — зачастую нормальный компромисс: и память не грузит, и пишется легко на голом ToList. И кэшики можно добавить если что. Для интерактивных списков это не зашвкарно короче. Но полезно понимать то как память с ними работает.
🦄 Стриминг стоит выбирать от бизнес-кейса (онлайн-логи, ответы LLM на лету, прогресс), а не «ради оптимизации памяти». Память он экономит — но это приятный бонус, а не повод.
#aspnet #dotnet #performance
Post #447
561



- 💯 3
- 🤝 2