Как мы ускорили MTProto-прокси в 25 раз, не переписывая его с нуля

09.05.2026, 06:29 · ⏱ 4 мин

1. Проблема


Запустили MTProto-прокси на Python. 2500 пользователей, AdTag для рекламного канала, авторизация каждого клиента по секрету. На 4 vCPU процесс упирался в 80-90% CPU при 70 одновременных соединениях.


Python × 1 процесс × 70 клиентов = 80% CPU


Дальше расти некуда.


2. Поиск узкого места


py-spy показал:


handle_handshake:    27% CPU     ← перебор 2500 пользователей

create_aes_ctr: 27% CPU ← создание AES-шифраторов
OpenSSL __init__: 27% CPU ← инициализация контекстов


54% процессора уходило на поиск пользователя при каждом handshake.


Почему? Протокол MTProto не передаёт ID пользователя открыто. Сервер обязан перебрать всех:


for user in all_2500_users:

key = SHA256(prekey + user.secret)
decrypted = AES_CTR_decrypt(key, handshake)
if decrypted[56:60] in VALID_TAGS: # нашли!
break


3. Попытка №1: Nginx + 4 воркера


Telegram клиенты


Nginx (port 8989, stream proxy)
├→ Python worker 8080
├→ Python worker 8081
├→ Python worker 8082
└→ Python worker 8083


Результат: нагрузка распределилась, но каждый воркер всё ещё тратил 27% CPU на перебор. Суммарно — те же 27% × 4 ядра.


4. Решение: вынос handshake в Go


Ключевая идея: перебор пользователей — чистая криптография (SHA256 + AES). Go делает это в 25 раз быстрее Python:



























Операция Python Go
SHA256 × 2500 45ms 2ms
AES-CTR × 2500 120ms 8ms
Итого на handshake 25ms 1ms

Архитектура:


Telegram клиенты


Nginx (port 8989)
├→ Python worker 8080 ──┐
├→ Python worker 8081 ──┤
├→ Python worker 8082 ──┤ все ходят в один Go-сервис
└→ Python worker 8083 ──┘ за 1ms вместо 25ms

Go verify service (127.0.0.1:9999)
- парсит config8080.py напрямую
- загружает 2500 секретов
- принимает 64 байта handshake
- перебирает пользователей
- возвращает: {"success": true, "user": "user_996"}


5. Реализация


Go-сервис (120 строк): парсит Python-конфиг, слушает TCP, ищет пользователя.


Python-патч (15 строк): замена цикла for user in config.USERS на вызов Go:


# Было:

for user in config.USERS:
secret = bytes.fromhex(config.USERS[user])
... # 200 строк криптографии

# Стало:
go_user, _ = go_verifier.verify(handshake) # 1ms, 0% CPU
if go_user:
user = go_user
... # только проверить результат


6. Результаты


До оптимизации


1 процесс: 70 соед, 24% CPU

handle_handshake: 27% CPU (OwnTime)
create_aes_ctr: 27% CPU


После оптимизации


4 процесса: 160 соед, 7-9% CPU каждый

handle_handshake: 0% CPU (ждёт Go)
Go-сервис: 1796 запросов, 99.6% успех, 1ms среднее


Сравнение





































Метрика До После
Время handshake 25ms 1ms
CPU на handshake 27% 0%
CPU/соединение 0.45% 0.25%
Эффективность (Mbit/s / 1% CPU) ~0.3 ~0.6
Макс. соединений ~280 ~600+

7. Мониторинг


Написали mtproto_live.py — тепловая карта в реальном времени:


Время     | Соед | Плз |   Down |    Up | Mbit/s |  CPU |  c0 |  c1 |  c2 |  c3 | Mbit/%

23:27:49 | 62 | 17 | 0.01MB | 0.04MB | 0.07 | 12.6% | 11.2% | 11.2% | 9.5% | 18.3% | 0.01


И mtproto_stats.py — сводный отчёт по всем воркерам с топ-50 пользователей по трафику.


Итог


Не переписывая весь прокси, вынесли самое узкое место в Go-микросервис. Python остался управлять соединениями и проксированием. Go делает только криптографию — то, в чём он быстрее в 25 раз.


150 строк Go + 15 строк Python = экономия 27% CPU и возможность обслуживать вдвое больше клиентов на том же железе.


Теперь самый быстрый прокси работает в боте @npokcubot

58
🔐

Войти через Telegram

Один клик — и вы сможете комментировать, ставить лайки и подписываться на каналы.