🌊 io.Reader и io.Writer: Лего для взрослых (и спасение от OOM) — 4 августа 2026 г. в 07:41:17.756
🌊 io.Reader и io.Writer: Лего для взрослых (и спасение от OOM) Знакомый сценарий: микросервис скачивает отчет из внешней системы. Когда вы его писали, отчет весил 5 МБ. Через полгода бизнес вырос, отчет весит 5 ГБ. Ваш под в Kubernetes ловит OOM (Out Of Memory) и бесславно умирает. Что делает в такой ситуации джуниор? Пытается увеличить лимиты памяти в Helm-чартах. Почему так вышло? Потому что код выглядит вот так: ❌ Плохо (Смерть памяти): resp, _ := http.Get(url) // Вычитываем ВСЕ 5 Гигабайт в оперативную память data, _ := io.ReadAll(resp.Body) os.WriteFile("report.csv", data, 0644) В Go есть два интерфейса, на которых держится вообще вся стандартная библиотека. Это io.Reader и io.Writer. В них всего по одному методу (Read и Write), но они превращают ваш код в систему сообщающихся сосудов. Суть этих интерфейсов - стриминг (потоковая передача данных). Нам не нужно держать в памяти весь файл, чтобы переложить его из сети на диск или отправить другому сервису. ✅ Хорошо (Стриминг через io.Copy): resp, _ := http.Get(url) defer resp.Body.Close() out, _ := os.Create("report.csv") defer out.Close() // Магия потока! io.Copy(out, resp.Body) Знаете, сколько оперативной памяти съест io.Copy при скачивании файла на 5 ГБ? Ровно 32 килобайта. Под капотом функция создает маленький буфер, читает в него кусок данных из сети и тут же сбрасывает на диск. И так по кругу, пока поток не иссякнет. 🔥 Нюансы для Senior-ов: Композиция (Пайплайны) Настоящая мощь io раскрывается, когда вы начинаете собирать эти интерфейсы в цепочки (паттерн Декоратор). Они вставляются друг в друга, как матрешки. Представьте задачу: нужно скачать файл, на лету распаковать его из GZIP и посчитать SHA256-хэш, не сохраняя ничего на диск. resp, _ := http.Get("http://example.com/data.gz") defer resp.Body.Close() // 1. Оборачиваем сетевой поток в GZIP-распаковщик (он тоже io.Reader!) gzReader, _ := gzip.NewReader(resp.Body) defer gzReader.Close() // 2. Создаем "писателя", который считает хэш hash := sha256.New() // 3. io.TeeReader читает из gzReader и параллельно дублирует данные в hash tee := io.TeeReader(gzReader, hash) // 4. Сливаем данные в "никуда" (io.Discard), // чтобы заставить насос качать данные по нашей трубе io.Copy(io.Discard, tee) fmt.Printf("Хэш файла: %x\n", hash.Sum(nil)) Мы только что обработали гигабайты сжатых данных за долю секунды, используя копейки RAM, ни разу не коснувшись жесткого диска. Интерфейсы io - этоUnix-философия, зашитая прямо в язык. Освойте их, и вы перестанете бояться огромных объемов данных. #golang #architecture #performance #io #bestpractices 👉 @golang_lib

