Нет времени объяснять, программируй!
Расскажу про опыт разработки собственных решений в Kinescope: когда стоит писать свой софт, какие проблемы это решает и какие сложности возникают.
TL;DR
- Сначала используйте готовое, потом пишите своё: на старте проекта важны сроки, не стоит начинать с разработки с нуля.
- Реальная необходимость — главный критерий: пишите свой софт, когда существующие решения не решают ваши конкретные проблемы и вы не можете их исправить.
- Понимание текущего решения обязательно: если не понимаете, как работает то, что используете, не сможете написать лучше.
- Ограничения — это хорошо: они помогают фокусироваться на том, что действительно нужно, и не реализовывать лишнее.
- Метрики и профилирование критичны: без них невозможно понять, что происходит и где проблемы.
- Стандартизация — ключ к масштабированию: единые шаблоны, библиотеки и практики позволяют быстро создавать новые сервисы без изобретения велосипедов.
Вступление: что означает «нет времени объяснять»
Доклад должен был быть про технологии, но получился больше про процессы и решения. В Kinescope мы пишем очень много софта сами — иногда даже того, который, казалось бы, писать не нужно, потому что он уже есть.
Сейчас у нас много собственных решений: DNS, CDN, система хранения контента, различные библиотеки для работы с медиаформатами, шаблоны сервисов, утилиты для работы с секретами. Это всё написано либо полностью с нуля, либо с использованием библиотек, но не коробочных продуктов.
«Нет времени объяснять» — это не про хаос и спешку. Это про скорость через ясность: когда вы понимаете проблему настолько глубоко, что объяснение займёт больше времени, чем решение. Когда у вас есть экспертиза, команда и понимание ограничений — можно просто взять и сделать.
Это не значит, что мы пишем всё подряд. Наоборот: мы очень консервативны в выборе того, что стоит делать самим. Но когда решение действительно нужно и готовое не подходит — мы не тратим время на долгие обсуждения, а просто делаем.
Контекст: откуда мы стартовали
Так было не всегда. Если вернуться в 2020 год: все ждали Олимпиаду в Токио, а мы начинали писать Kinescope. В тот год все сидели по домам, и мы писали.
Мы начинали с проверенных технологий: Google DNS для гео-балансировки, FreeBSD + nginx, приложения на Go, PostgreSQL и ClickHouse. У нас своё железо в нескольких дата-центрах, BGP/anycast, классический стек.
Что важно на старте проекта
При запуске проекта важны сроки. Мы были в состоянии стартапа, нам нужно было быстро запуститься, и точно мы не будем что-то ставить сами.
Как этого добиться? Достаточно просто:
Понятные цели и задачи — мы прекрасно понимали, какой должен быть продукт и что он должен делать. У нас было понимание задач, потому что мы и до Kinescope занимались похожими проектами.
Надёжная опытная команда — команда была до Kinescope, и мы начали этот проект с надёжными проверенными людьми.
Проверенные технологии — мы не стали ничего думать, посмотрели, с чем работали до этого, что хорошо знаем, и стали это использовать.
Наш начальный стек
Команда у нас была, но это не значит, что проблем не будет. У нас есть архитектурные проблемы, кто-то что-то забыл, не подумал. Но самое главное по опыту — с чем мы сталкиваемся:
Разработчики не знают SQL и не умеют думать о базе данных. Из этого вылезает самое большое количество проблем. Поэтому хотелось бы, помимо знания языка программирования, чтобы знали что-то ещё.
Мы взяли проверенные технологии. Для DNS нужен был гео-DNS, мы взяли Google DNS. Важное уточнение: у нас своё железо было изначально, то есть мы не используем cloud. У нас своя железная платформа, мы находимся в нескольких дата-центрах, и это было практически с самого старта проекта.
Поэтому у нас BGP/anycast на точках, на машинах, которые анонсируют, стоят Bird, под ними стоит nginx, под nginx написаны приложения на Go. Приложения общаются с PostgreSQL для аналитики, у нас был ClickHouse для хранения логов. Также у нас есть очереди и другие компоненты.
Достаточно классический стек, с которым мы работали и до этого. Это FreeBSD и nginx — это хорошая связка, её в своё время использовал Netflix. Сейчас есть компании, которые тоже занимаются видео, и они тоже используют FreeBSD.
Что получилось
Всё получилось — серьёзных проблем не было.
Мы рассказывали, что мы платформа для интернета, объединяем технологии. Сейчас у нас большое количество клиентов, в 2023 году нас используют очень многие. Если вы пользуетесь интернетом, то с высокой вероятностью вы так или иначе сталкиваетесь с нами — где-то видите видео, которое раздаётся через нас, где-то смотрите онлайн-трансляции, которые мы обслуживаем.
Рост и новые задачи
Мы начали расти. Рост бывает разный, и у нас он случился. У нас начали появляться новые клиенты, сформировалась база. Вместе с клиентами к нам пришла нагрузка, и она начала расти.
У нас практически не поменялась команда — не сильно. Стало больше разработчиков, которые занимаются фронтендом и плеерами, стало больше дизайнеров. Но команда как таковая не сильно росла. Она выросла в очень хорошем направлении: появился человек, который занимается медиаформатами на Go — это нам на пользу.
Задачи стали расти. Бизнес растёт, поддержка нужна. Вроде бы всё хорошо, но времени не хватает — работаем над задачами, обслуживаем клиентов.
Когда писать своё: decision framework
Где в этом месте взять время или зачем что-то переписывать? Если оно работает, работает относительно неплохо, приносит деньги — зачем ввязываться в разработку какого-то кастома, который можно взять и поставить?
Почему появляется необходимость писать свой софт
Зачем вообще появляется необходимость что-то написать? Это может быть хобби, обучение, развитие. Иногда так и делают просто потому, что могут. С этим мы тоже сталкиваемся, причём не только в российском сообществе, но и с иностранными компаниями, даже очень большими.
Реальная необходимость возникает не только у нас. Иногда из этого вырастают очень хорошие серьёзные продукты, которыми мы пользуемся, а потом отказываемся и пишем что-то своё.
Хорошие примеры: Nginx появился под реальные задачи, писался людьми, которые сталкивались с проблемами и решали их. ClickHouse — та же история. ScyllaDB и InnoDB — отличные решения, их любят все, даже те, кто пользуется PostgreSQL, потому что это произведение искусства. Советую почитать код, если интересно занимаетесь базами данных или работой с дисками — это хороший материал для изучения.
ScyllaDB тоже образец инженерного искусства, как и многое, что они делают, включая фреймворки, которые выпускают.
Критерии принятия решения
Как понять, что время пришло что-то взять и переписать? У вас должна быть какая-то проблема, и существующее решение не устраивает.
Нельзя сказать «он меня не устраивает, мне не нравится, потому что у него логотип зелёный» — это не аргумент. Нужно назвать конкретные проблемы, с которыми вы столкнулись, и главное — вы не можете это решить никаким способом. Компетенции не хватает, например, в языке или экосистеме. Вы не можете там что-то сделать, не можете поправить, потому что архитектурно это не проблема софта, который вы используете — архитектура не позволяет внести коррективы, которые нужны только вам, нет API.
Критерии, которые мы используем:
- Нагрузка и производительность: существующее решение не справляется с вашей нагрузкой, и оптимизация не помогает.
- Латентность: требования к задержкам не выполняются, и это критично для бизнеса.
- Стоимость: готовое решение слишком дорогое (лицензии, инфраструктура, поддержка).
- Экспертиза: у вас есть команда, которая может написать лучше или адаптировать под ваши нужды.
- Риски: зависимость от внешнего решения создаёт риски (vendor lock-in, изменения в API, прекращение поддержки).
- Уникальные требования: вам нужна функциональность, которой нет в готовых решениях и которую нельзя добавить.
Красные флаги (когда НЕ стоит писать своё):
- Нет конкретной проблемы — просто «хочется попробовать».
- Нет экспертизы в области — вы не понимаете, как работает текущее решение.
- Нет времени на поддержку — написать проще, чем поддерживать.
- Готовое решение покрывает 90%+ ваших потребностей — остальное можно решить костылями.
- Команда слишком маленькая — не хватит ресурсов на разработку и поддержку.
Trade-offs: build vs buy
┌──────────────────────────────────────────────────────────────┐
│ Build vs Buy │
├──────────────────────────────────────────────────────────────┤
│ │
│ Писать своё, если: Использовать готовое, если: │
│ │
│ ✓ Уникальные требования ✓ Стандартные задачи │
│ ✓ Критичная производительность ✓ Время важнее │
│ ✓ Нет подходящего решения ✓ Есть проверенное │
│ ✓ Есть экспертиза ✓ Нет экспертизы │
│ ✓ Долгосрочная перспектива ✓ Быстрый прототип │
│ ✓ Контроль критичен ✓ Готовы к компромиссам │
│ │
└──────────────────────────────────────────────────────────────┘
Важно: решение не всегда бинарное. Часто правильный подход — взять готовое и адаптировать, или написать только критичную часть, используя готовые компоненты.
Что должно быть на момент принятия решения
Понимание, как работает текущее решение. Если вы не понимаете, как работает софт, который используете, то не сможете написать что-то лучше, изменить или поправить. Это логично, но не всегда так на практике.
Практическая заметка. Перед тем как писать собственное решение, важно глубоко понять текущее. Это не только про чтение документации, но и про изучение исходного кода, профилирование и понимание архитектурных решений. Без этого понимания легко создать решение, которое будет хуже существующего. Источник: опыт разработки собственных решений в Kinescope.
Понятный список требований — описание того, что должно получиться. ТЗ и документация не должны появиться только после того, как вы что-то разрабатываете. Это комплексный процесс — нельзя просто сидеть вечером и писать с нуля. У вас всегда есть зарисовки на салфетке, планы, диаграммы — это нормально.
Должен быть список ограничений. Когда вы разрабатываете технологию под бизнес, у вас должно быть понимание, что решение может не решить, или что можно выкинуть. Ограничения — это хорошо, и мы ими активно пользуемся в наших решениях, потому что выкидываем много того, что нам не нужно, и не реализуем лишнее.
Принципы инженерии: как мы строим решения
Когда мы пишем своё, мы следуем нескольким ключевым принципам, которые помогают создавать решения, которые действительно решают проблемы, а не создают новые.
Унификация: один сервис — один бинарник
Один из самых важных принципов — унификация. Каждый сервис — это один бинарный файл, который содержит всё необходимое. Нет зависимостей от интерпретаторов, библиотек или конфигурационных файлов, которые нужно устанавливать отдельно.
Это не только про простоту деплоя, но и про предсказуемость: если сервис работает на одной машине, он будет работать и на другой. Если вы знаете, как работает один сервис, вы понимаете, как работают все остальные.
Минимизация слоёв и точек отказа
Каждый дополнительный слой — это дополнительная точка отказа, дополнительная задержка, дополнительная сложность в отладке. Мы стараемся минимизировать количество слоёв между клиентом и данными.
Это не значит, что мы отказываемся от балансировщиков или прокси — они нужны. Но мы не добавляем их просто так, «на всякий случай». Каждый компонент должен решать конкретную задачу, и если его можно убрать без потери функциональности — мы его убираем.
Наблюдаемость как контракт
Если вы не видите, что происходит внутри вашего сервиса, вы не можете понять, почему он работает медленно или падает. Поэтому наблюдаемость — это не опция, а обязательное требование.
Каждый сервис должен экспортировать:
- Метрики (Prometheus) — производительность, ошибки, использование ресурсов
- Логи (структурированные) — что происходит, с каким контекстом
- Health checks — готов ли сервис принимать трафик
Без этого вы летите вслепую. Подробнее о том, как мы строим наблюдаемость, см. в статье «Эксплуатация без K8s».
Ограничения как преимущество
Ограничения — это не недостаток, а преимущество. Когда вы знаете, что ваше решение НЕ делает, вы можете:
- Упростить код, убрав ненужную функциональность
- Оптимизировать под конкретные сценарии использования
- Сделать решение быстрее и надёжнее
Например, наш CDN для лайва не поддерживает HTTP Range — потому что нам это не нужно. Это позволило упростить код и сделать его быстрее.
Кейсы: что мы пишем сами и почему
Кейс 1: Шаблоны сервисов — стандартизация как основа масштабирования
Проблема: десятки сервисов с разными подходами к конфигурации, логированию и метрикам — поддержка превращается в кошмар.
Почему готовое не подошло: готовые шаблоны либо слишком общие, либо заточены под конкретный фреймворк. Нужен баланс: гибкость + единый стандарт.
Решение: набор шаблонов (dev/templates) для HTTP, gRPC и фоновых сервисов. Стандартизируют структуру проекта, CLI, секреты, метрики, сборку и деплой.
Каждый шаблон включает:
- Готовую структуру проекта
- Примеры обработчиков и бизнес-логики
- Интеграцию с базой данных (PostgreSQL, ClickHouse)
- Метрики и health checks
- Makefile для сборки и тестирования
spec.ymlдля описания деплоя
Эксплуатация: когда нужно создать новый сервис, разработчик клонирует шаблон, запускает bootstrap-скрипт и получает готовый каркас со всеми стандартными практиками. Это экономит время и гарантирует, что все сервисы работают одинаково.
Цена решения: поддержка шаблонов требует времени, но это время окупается за счёт того, что новые сервисы создаются быстрее и работают предсказуемее. Когда мы обновляем шаблоны (например, добавляем новую практику), все новые сервисы автоматически получают эти улучшения.
Кейс 2: Секреты через Vault + env — безопасность без сложности
Проблема: секреты нужно хранить безопасно, но они должны быть доступны приложению. Классические подходы небезопасны, HashiCorp Vault добавляет сложность.
Почему готовое не подошло: готовые решения либо слишком сложные, либо слишком простые. Нужно что-то среднее: безопасно, но без лишних зависимостей.
Решение: используем Ansible Vault для шифрования секретов и библиотеку go/vault для расшифровки. Секреты шифруются через Ansible Vault, кодируются в base64, пароль вшивается в бинарник через -ldflags. При запуске приложение автоматически расшифровывает переменные окружения при старте.
Эксплуатация: секреты хранятся в зашифрованном виде в Ansible inventory, при деплое подставляются в spec.yml. Для локальной разработки можно использовать SKIP_DECRYPT=true.
Цена решения: простая библиотека, интеграция с существующей инфраструктурой без новых зависимостей.
Подробнее см. «Эксплуатация без K8s».
Кейс 3: CDN — когда nginx перестал справляться
Проблема: nginx работал, но с ростом нагрузки появились проблемы: сложно сегментировать трафик, добавлять нужные метрики и логи, возникали проблемы с ростом объектов в кэше.
Почему готовое не подошло: Varnish ближе, но написан на C (размытие стека), проблемы с хранением при больших объёмах, метрики нужно допиливать. Команда пишет на Go — проще написать свой.
Решение: разделили ответственность: прокси (маршрутизация), storage (чтение с диска), manager (конфигурация), collector (метрики и логи). Это дало полный контроль над метриками, логированием и оптимизацией под наши задачи.
Подробнее о том, как мы строили CDN для раздачи контента с HDD, см. «Раздача контента с HDD».
Кейс 4: Storage — доменная сложность и управляемость
Проблема: нужна система хранения с автоматической балансировкой нагрузки между дисками и обработкой отказов. Ceph слишком сложный (решает задачи, которые нам не нужны), MinIO заточен под S3, а нам нужны специфичные оптимизации.
Почему готовое не подошло: нам нужна простая модель — каждый сервер хранит файлы локально, балансировка на уровне приложения. Готовые решения не дают нужной управляемости.
Решение: свой storage-сервис (kineceph) с автоматическим обнаружением дисков, взвешенной случайностью для выбора диска при записи, обработкой отказов, метаданными в PostgreSQL и поддержкой объединения дорожек.
Эксплуатация: автоматическое управление дисками, метрики для диагностики.
Цена решения: полный контроль и возможность оптимизировать под конкретные задачи.
Кейс 5: DNS — гео-балансировка и автоматизация
Проблема: нужен DNS-сервер с гео-балансировкой, health checks, failover groups и автоматической выдачей сертификатов Let’s Encrypt через DNS-01.
Почему готовое не подошло: BIND не умеет гео-балансировку и health checks без плагинов, PowerDNS требует сложной интеграции с нашей инфраструктурой.
Решение: свой DNS-сервер (kinescope/dns) с гео-записями через MaxMind GeoIP2-City, автоматическими health checks, failover groups, обработкой DNS-01 челленджей, метриками Prometheus и поддержкой UDP, TCP и DNS-over-TLS. Конфигурация через HCL.
Эксплуатация: автоматическое обновление конфигурации, health checks, управление сертификатами.
Цена решения: полный контроль над DNS и возможность быстро адаптировать под новые требования.
Кейс 6: Go-библиотеки — платформенные решения
Проблема: когда сервисов много, обвязка неизбежно расползается: в каждом сервисе по‑своему настраивают HTTP, таймауты, ретраи, логирование, метрики, валидацию, сериализацию ответов и работу с секретами. Итог — дублирование кода, разные подходы к одним и тем же задачам и дорогая поддержка.
Почему готовое не подошло: стандартная библиотека Go отличная — но она даёт кирпичики, а не готовый платформенный стандарт. Сторонние пакеты часто закрывают один кусок, не всегда стыкуются по подходу между собой и не учитывают наши требования к эксплуатации и наблюдаемости.
Решение: мы собрали набор внутренних Go‑библиотек — по сути свою «стандартную библиотеку» поверх stdlib Go. Это не про «изобрести всё заново», а про единый, повторяемый способ делать базовые вещи: сетевое взаимодействие, HTTP‑сервер и клиент, работу с секретами, валидацию, рендеринг ответов, гео‑утилиты и так далее.
Принципы: небольшие пакеты с узкой ответственностью, Go‑идиомы и интерфейсы для естественного использования, наблюдаемость по умолчанию (метрики и логи там, где это важно), безопасные и предсказуемые дефолты, тесты и понятная эволюция API.
Эксплуатация и эффект: новые сервисы стартуют быстрее и выглядят одинаково «снаружи». Мы улучшаем библиотеку один раз — и улучшение доходит до всех сервисов, вместо десятка разношёрстных правок. Это снижает когнитивную нагрузку, ускоряет онбординг и упрощает поддержку.
Цена решения: библиотека — тоже продукт: её надо документировать, поддерживать совместимость и иметь владельца. Но на масштабе большого числа сервисов это окупается.
Дальше — конкретный пример того, как этот подход выглядит на практике: почему мы не использовали стандартный HTTP‑сервер.
Возвращаемся к Go: технические детали
Вернёмся к техническим деталям работы с Go.
Почему не стандартный HTTP-сервер
Стандартный HTTP-сервер Go хороший, но не хватает возможностей для наших задач: нужны пул открытых TCP-коннектов с кастомными таймаутами, кэш открытых файлов, zero-copy для передачи данных. Стандартный сервер копирует данные через user space, что даёт лишние накладные расходы.
Прокси и zero-copy
Прокси принимает трафик, разбирает HTTP, отправляет по TCP. Проблема: стандартный HTTP ResponseWriter копирует данные через user space, что даёт задержки (200+ мс по процентилям)1.
В Go есть интерфейс io.Reader с методами Seek (для файлов) или WriteTo (для сети) — если реализован, Go использует оптимизированный путь. Но стандартный ResponseWriter не даёт доступа к Body для реализации этих интерфейсов.
При zero-copy мы говорим ОС: вот два дескриптора, сама разберись. Это работает и для файлов, и для соединений — не боремся с GC, не создаём лишних объектов. При передаче нескольких гигабит в секунду потребление памяти практически нулевое.
Сложности: работа с памятью
Garbage Collector — краеугольный камень. Когда у нас было десятки миллионов объектов (дерево для индекса), GC давал паузы и всё переставало работать. Решение: заменили дерево на обычный map — всё заработало.
Используем sync.Pool для переиспользования объектов и байт — GC их не трогает, мы переиспользуем постоянно. Больших проблем с памятью нет.
TLS и производительность
Стандартная библиотека Go для TLS хорошая, но очень медленная для больших объёмов трафика — проигрывает nginx/терминаторам в разы, съедает весь CPU. Профилирование показывает кучу памяти и проблемы с GC2.
Решение: kTLS — kernel TLS, ядро Linux понимает TLS. Можно терминацию делать в ядре или даже на сетевой карте (Intel). Очень производительно, но в Go этого нет — есть issue на GitHub, но решения нет.
Мы написали свою реализацию kTLS для Go. Подробнее см. «Разгоняем Go TLS до 100 Gbps».
Отдача из памяти: не всегда то, что кажется
При пиковых нагрузках (лайв-трансляции +150 Гбит) заметили: отдача из памяти провисает сильнее, чем с диска. Причина: при отдаче из памяти копируем данные из user space в kernel space, а при отдаче с диска используем zero-copy — данные не попадают в память приложения.
Решение: серверы для лайва стали проще — убрали поддержку HTTP Range, persistent cache и шардирование. Кольцевой буфер: пишем с начала до конца, при переполнении затираем старое. Максимально простое решение.
Чеклисты: готовность к продакшену
Когда мы создаём новый сервис, мы проверяем его по нескольким чеклистам, чтобы убедиться, что он готов к продакшену. Подробнее о том, как мы эксплуатируем сервисы без Kubernetes, см. в статье «Эксплуатация без K8s».
Чеклист: готов ли сервис к продакшену без кубера
- Один бинарник: сервис собирается в один бинарный файл без внешних зависимостей
- Systemd unit: есть systemd unit файл с правильными настройками (restart, limits, security)
- Health checks: сервис экспортирует
/healthили/pingendpoint - Метрики: сервис экспортирует метрики Prometheus на
/metrics - Логирование: логи структурированные (JSON) и отправляются в централизованную систему
- Graceful shutdown: сервис корректно обрабатывает SIGTERM/SIGINT
- Конфигурация: конфигурация через переменные окружения или CLI флаги
- Секреты: секреты передаются безопасно (через vault или переменные окружения)
- Мониторинг: настроены алерты на критические метрики
- Документация: есть документация по деплою и эксплуатации
Чеклист: что обязательно стандартизировать
Когда создаёте новый сервис, убедитесь, что он следует стандартам:
- Порты: используйте стандартные порты (HTTP: 8000+, мониторинг: 9000+)
- Метрики: все метрики в формате Prometheus с единым namespace
- Логирование: структурированные логи с единым форматом (JSON)
- Build info: версия, коммит, дата сборки доступны через метрики или
/version - Rollback: можно откатиться к предыдущей версии без проблем
- Спецификация: есть
spec.ymlс описанием деплоя (порты, лимиты, зависимости)
Итоги
Главные уроки:
- Пишите своё только когда это действительно нужно и вы понимаете, почему готовое не подходит.
- Стандартизируйте всё, что можно стандартизировать — шаблоны, библиотеки, практики.
- Инвестируйте в платформенные решения, которые упростят жизнь всем.
- Ограничения — это хорошо: они помогают фокусироваться на важном.
- Метрики и профилирование критичны — без них невозможно понять, что происходит. В Go это даёт огромный профит.
- Проверяйте всё: статьи, бенчмарки, библиотеки — не всегда работают так, как описано.
Полезные материалы
- Zero-copy в Linux: sendfile и splice — техническая статья о механизмах zero-copy и их применении для раздачи файлов
- Linux Network Performance Ultimate Guide — полное руководство по тюнингу сетевой производительности: настройки ядра, TCP, буферов и драйверов
- HTTP/2 Prioritization with NGINX — как Cloudflare решает проблемы приоритизации HTTP/2 и оптимизирует раздачу контента
- The Story of One Latency Spike — практический пример диагностики проблем производительности: как искать узкие места в системе
- Go pprof: профилирование производительности — официальное руководство по использованию pprof для профилирования Go-приложений
- Работа с памятью в Go: GC и оптимизации — руководство по пониманию работы garbage collector и оптимизации использования памяти
- ScyllaDB: работа с дисками в Linux — практические советы по работе с дисками, mmap и его trade-offs от команды ScyllaDB
- kTLS: Kernel TLS для Linux — документация по kTLS, которая помогает решить проблемы производительности TLS в Go
- Когда писать свой софт, а когда использовать готовый — размышления о балансе между собственными решениями и готовыми продуктами
- DNS: основы и гео-маршрутизация — как использовать DNS для маршрутизации трафика и построения CDN
Практическая заметка. При разработке собственных решений важно понимать текущее решение и иметь чёткий список требований. Ограничения — это хорошо: они помогают фокусироваться на том, что действительно нужно, и не реализовывать лишнее. Метрики и профилирование критичны: без них невозможно понять, что происходит в системе. Стандартизация — ключ к масштабированию: единые шаблоны, библиотеки и практики позволяют быстро создавать новые сервисы без изобретения велосипедов. Источник: опыт разработки CDN, DNS и систем хранения данных.
Сноски
О том, как zero-copy работает в Linux и почему это критично для производительности, см. «Разгоняем Go TLS до 100 Gbps» и «Раздача контента с HDD». ↩︎
О проблемах производительности TLS в Go и решении через kTLS см. «Разгоняем Go TLS до 100 Gbps». О проблемах с TLS в контексте раздачи контента см. также «Раздача контента с HDD». ↩︎