Skip to content

Оптимизация потребления памяти на соединение (~40%) #412

@dolonet

Description

@dolonet

Привет!

Решил проверить, могу ли я сделать более экономичной юнит-экономику по соединениям — оперативка нынче дорогая :) и по сути главный лимитирующий фактор для количества одновременных соединений.

Что нашёл:

Замерил расход памяти на одно соединение. Основные потребители — горутинные стеки, которые раздуваются из-за stack-allocated массивов по 16 КБ и больше не сжимаются:

Компонент Расход
pump() × 2 — var buf [MaxRecordPayloadSize]byte на стеке ~64 КБ (стеки растут до 32 КБ каждый)
doppel.start()buf := [MaxRecordSize]byte{} на стеке ~32 КБ
Clock.Start() — отдельная горутина + канал на соединение ~2-4 КБ
ctx.Done() watcher горутины в relay и proxy ~4 КБ
Итого стеки ~100 КБ

Плюс TLS/doppel heap-буферы (~13 КБ) и прочее. В сумме ~120 КБ на соединение.

Что сделал:

  1. sync.Pool для relay буферов + уменьшение до 4 КБ. 16 КБ буфер не нужен, потому что TLS-слой сам собирает TLS records во внутреннем readBuf — relay просто читает из него порциями. 4 КБ не увеличивает число syscalls, только больше итераций io.CopyBuffer (overhead — наносекунды на interface dispatch).

  2. sync.Pool для doppel буфера. Перенос 16 КБ массива из стека в пул.

  3. Inline Clock в start(). Вместо отдельной горутины с каналом — time.Timer прямо в цикле start(). Семантика та же: таймер стреляет → обработка → reset. Backpressure сохраняется, т.к. Reset только после завершения итерации.

  4. context.AfterFunc вместо горутин в relay и proxy. Вместо горутин, висящих на <-ctx.Done() всё время жизни соединения — context.AfterFunc, который создаёт горутину только в момент отмены.

Результат в проде:

Метрика До После
Горутин на соединение 4-5 2-3
Стеки горутин (суммарно) ~100 КБ ~12 КБ
RSS на соединение (замер) ~160 КБ ~93 КБ
Экономия ~42%

Замерял так: RSS процесса минус baseline (RSS при 0 соединениях), делим на число соединений. Тестировал при ~50-60 активных соединениях.

Все тесты проходят, включая -race. В проде крутится несколько часов без проблем.

У себя в форке реализовал: dolonet#1

Если идея ок, могу подготовить чистый PR в mtg.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions