🌊 io.
Знакомый сценарий: микросервис скачивает отчет из внешней системы. Когда вы его писали, отчет весил 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