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