🔥 Веб-сервер для тех, кто даже собаку не смог обучить базовым командам
🐕 Сравнение #1: Даже собака понимает цикл лучше тебя
Моя собака знает: если я НЕ сказал "место" — игра продолжается. Она не спрашивает каждые 5 секунд: "Хозяин, мы всё ещё играем?"
Веб-сервер работает так же:
- Запустился → крутится в цикле → ждёт запросы
- Пока ты не пошлёшь SIGTERM/SIGKILL — он жив
- Пока он жив — файлы, сокеты, память — ВСЁ занято
# Типичный код джуна, который "не понимает, почему файл занят"
while True: # <-- ВОТ ЭТО, БЛЯТЬ, И ЕСТЬ "ПРОЦЕСС НЕ ЗАКРЫВАЕТСЯ"
connection = server.accept()
handle_request(connection)
# connection.close() <-- А ВОТ ЭТОГО НЕТ, ПОТОМУ ЧТО ТЫ МУДАК
📚 Теория для тех, кто прогулял Computer Science
Что такое веб-сервер (объясняю как пятилетнему)
Веб-сервер — это программа, которая:
- Слушает порт (обычно 80/443)
- Принимает TCP-соединения
- Парсит HTTP-запросы
- Отдаёт ответы
- ВОЗВРАЩАЕТСЯ К ПУНКТУ 2, А НЕ СДЫХАЕТ
❌ Типичные заблуждения (или "Почему твой код — говно")
Заблуждение #1: "Процесс закрылся — всё почистилось"
Реальность:
# Ты: $ kill -9 12345 # Убил процесс # Система: - Сокеты в TIME_WAIT висят 60 секунд - Файловые дескрипторы освободились (ура!) - Но порт занят (сюрприз, мудак!) - Shared memory осталась (потому что ты не unlink сделал)
Заблуждение #2: "Мой код правильно закрывает соединения"
# Код Васи (Senior Developer, 3 года опыта):
def handle_client(client_socket):
data = client_socket.recv(1024)
response = process(data)
client_socket.send(response)
# client_socket.close() <-- ГДЕ, БЛЯТЬ?!
# Результат через неделю:
# - 50,000 открытых сокетов
# - "Too many open files" в логах
# - Вася: "Это баг в Linux!"
Правильно:
def handle_client(client_socket):
try:
with client_socket: # <-- КОНТЕКСТНЫЙ МЕНЕДЖЕР, ЭТО PYTHON 101
data = client_socket.recv(1024)
response = process(data)
client_socket.send(response)
except Exception as e:
log.error(f"Вася опять наебнулся: {e}")
finally:
# Даже если ты долбоёб — сокет закроется
pass
🔥 Жизненный цикл веб-сервера (для слабоумных)
Фаза 1: Инициализация
1. Открыть серверный сокет 2. bind() на порт (если порт занят — иди нахуй) 3. listen() с backlog (очередь подключений) 4. Настроить обработчики сигналов (SIGTERM, SIGINT)
Что делает Вася:
server = socket.socket()
server.bind(('0.0.0.0', 8080)) # Порт занят? "Перезагрузи сервер!" (с)Вася
server.listen(5) # Backlog=5? При 100 RPS будет пиздец
Что делает нормальный человек:
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # <-- ВОТ ЭТО
try:
server.bind(('0.0.0.0', 8080))
except OSError as e:
if e.errno == 48: # Address already in use
print("Порт занят, долбоёб. Найди процесс: lsof -i :8080")
sys.exit(1)
server.listen(128) # Адекватный backlog
Фаза 2: Главный цикл (THE FUCKING LOOP)
while running: # <-- ВОТ ПОЧЕМУ "ПРОЦЕСС НЕ ЗАКРЫВАЕТСЯ"
client, addr = server.accept()
threading.Thread(target=handle_client, args=(client,)).start()
Проблемы этого говнокода:
- ❌ Неограниченное количество потоков → OOM через час
- ❌ Нет graceful shutdown → клиенты получат RST при остановке
- ❌ Нет таймаутов → один медленный клиент заблокирует поток навсегда
Фаза 3: Graceful Shutdown (или "Почему Netflix работает, а ты нет")
import signal
import threading
running = True
active_connections = []
def signal_handler(signum, frame):
global running
print("Получил сигнал, завершаюсь...")
running = False
# Ждём завершения активных соединений
for conn in active_connections:
conn.shutdown(socket.SHUT_RDWR)
conn.close()
server.close()
sys.exit(0)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
# Теперь Ctrl+C не оставит висячих соединений
Вася делает:
$ kill -9 $(pidof myserver) # "Быстро и надёжно!" # Клиенты: Connection reset by peer # DBA: Незакоммиченные транзакции # Ops: Corrupted data
💀 Реальные пиздецы из production
Case #1: "Почему у нас 50,000 открытых файлов?"
(CTO стартапа, 2022):
@app.route('/logs')
def get_logs():
f = open('/var/log/app.log', 'r') # <-- НЕТ CLOSE()
return f.read()
# Через 3 дня:
# OSError: [Errno 24] Too many open files
Стоимость ошибки:
- Downtime: 4 часа
- Потерянная выручка: $7,000
- Репутация: похоронена
Источник: Судя по всему, реальный кейс из моей практики, потому что такую хуйню я видел N раз.
Case #2: "Thundering Herd" или "Как положить сервер одной кнопкой"
# Код Васи:
while True:
clients = select.select([server], [], [], 0.1) # <-- 0.1 сек таймаут
if clients:
# Обработка...
# Проблема: При 1000 RPS процесс просыпается 10,000 раз в секунду ВПУСТУЮ
# CPU: 80% на context switching
# Latency: 500ms вместо 10ms
Правильно (epoll):
epoll = select.epoll()
epoll.register(server.fileno(), select.EPOLLIN)
while True:
events = epoll.poll(timeout=-1) # <-- Блокируется до события
for fd, event in events:
# Обработка
Статистика (Google, 2018):
Event-driven архитектура снижает latency на 60% при той же нагрузке по сравнению с thread-per-connection.
🐕 Сравнение #3: Обучаемость
Дрессировка собаки:
- 10-20 повторений → команда усвоена
- Положительное подкрепление → навык закреплён
Обучение Васи:
- 10 багов в production → "Это фреймворк виноват"
- 20 инцидентов → "Linux не для production"
- Code review с 50 комментариями → "Ты просто придираешься"
Формула:
Тупость_джуна × Самоуверенность = Ущерб² Где Ущерб измеряется в часах downtime
📊 Конкретная статистика (для тех, кто верит только цифрам)
Стоимость некорректного управления ресурсами
Проблема Prevalence Avg. Fix Time Business Impact File descriptor leaks 34% проектов (Stack Overflow Survey, 2023) 4-8 часов $15K-$50K/incident Неправильный shutdown 67% стартапов 2 недели рефакторинга Data corruption в 12% случаев Отсутствие таймаутов 78% junior-кода "Пусть так работает" (никогда не фиксят) Cascade failures Источники:
- The C10K problem (Dan Kegel, 1999) — если ты этого не читал, ты нахуй не нужен
- Handling 1M websockets (Phoenix Framework, 2015)
💊 Медицинская метафора: Диагностика умирающего сервера
Симптомы:
# Температура (CPU) $ top PID USER PR NI VIRT RES SHR S %CPU %MEM 12345 app 20 0 5000000 3000000 10000 R 250.0 60.0 # Диагноз: Жар, скорее всего инфекция (бесконечный цикл) # Пульс (Open files) $ lsof -p 12345 | wc -l 50347 # Норма: < 1000. У тебя: СЕПСИС # Дыхание (Network) $ netstat -an | grep ESTABLISHED | wc -l 4891 # Половина соединений в CLOSE_WAIT — некроз тканей
Лечение:
# Хирургия (рефакторинг):
class Server:
def __init__(self):
self.active_connections = []
self.max_connections = 1000
self.connection_timeout = 30
def accept_connection(self):
if len(self.active_connections) >= self.max_connections:
# "Извините, больница переполнена"
raise ServerOverloadedError()
def cleanup_dead_connections(self):
now = time.time()
self.active_connections = [
conn for conn in self.active_connections
if (now - conn.last_activity) < self.connection_timeout
]
🎭 Диалог-карикатура: Как рождается пиздец
PM Петров (10:00):
"Привет! Нам нужно добавить real-time обновления. Просто WebSocket воткни, это 5 минут!"
Разраб (10:05):
"Петров, у нас blocking I/O. Надо переписать на async."
PM (10:06):
"Не усложняй! Просто добавь ещё один поток."
Разраб (10:07):
"У нас уже 5000 потоков. Server падает с OOM."
PM (10:08):
"Ну добавь памяти! У нас же Kubernetes, всё масштабируется!"
3 дня спустя:
[CRITICAL] OutOfMemoryError [CRITICAL] Too many open files [CRITICAL] Connection pool exhausted [ALERT] AWS bill: $87,000 (prev month: $8,000)
PM (13:00):
"Почему production упал? Ты же Senior!"
⚠️ Раковая опухоль: Технический долг
Как это начинается:
# Commit 1 (Вася, 2020): "Quick fix"
def handle_request(req):
# TODO: add timeout
# TODO: close connection properly
# TODO: handle errors
result = process(req)
return result
3 года спустя:
# Commit 847 (Вася, 2023): "URGENT FIX"
def handle_request(req):
# FIXME: memory leak here
# HACK: sometimes crashes, but works
# XXX: don't touch this, it's magic
# TODO from 2020: lol забыл
try:
# ... 500 строк говна
pass
except:
pass # "Обработка ошибок"
Метастазы:
- Невозможно добавить новую фичу (всё ломается)
- Тесты не работают (их нет)
- Никто не понимает, как оно вообще работает
- "Давайте перепишем с нуля!" (спойлер: не перепишут)
🔪 Советы для долбоёбов-менеджеров
✅ Делай:
- Найми нормального архитектора
- Не путай 10 лет опыта с 1 годом, повторённым 10 раз
- Если кандидат не знает, что такое epoll — шли нахуй
- Дай время на рефакторинг
- Не "когда будет время" (никогда)
- Выделяй 20% спринта на технический долг
- Читай ебучие метрики
- Open files > 1000? Проблема.
- CPU > 80%? Проблема.
- "Всё работает" ≠ всё хорошо
❌ НЕ делай:
- ~~"Просто добавь поле в базу"~~ → Design review
- ~~"Почему так долго? Это же 5 строк!"~~ → Заткнись
- ~~"У конкурентов уже есть!"~~ → У конкурентов есть бюджет $10M и 50 разрабов
💀 Советы для разрабов, которые ещё не сдохли
1. Изучи основы (блять, наконец-то)
Обязательный минимум:
- TCP/IP (не википедия, а RFC793)
- HTTP/1.1, HTTP/2 (разница критична)
- Event loop (libuv, tokio, async/await)
- Process vs Thread vs Coroutine
Книги (реальные, не Medium посты):
- "Unix Network Programming" (Stevens) — библия
- "High Performance Browser Networking" (Grigorik)
- "The Linux Programming Interface" (Kerrisk)
2. Научись профилировать
# Не "кажется тормозит", а: $ perf top # CPU hotspots $ strace -c ./myserver # Syscall overhead $ lsof -p $(pidof myserver) # File descriptors $ netstat -anp | grep myserver # Network state
3. Graceful degradation
# НЕ:
def handle():
result = slow_external_api() # Висит 30 секунд
return result
# А:
def handle():
try:
result = slow_external_api(timeout=1.0)
except TimeoutError:
return cached_result() # Degraded, но работает
4. Тестируй под нагрузкой
# Твой "тест": $ curl http://localhost:8080 # OK (1 запрос) # Реальный тест: $ wrk -t12 -c400 -d30s http://localhost:8080 # Результат: Server crashed after 3 seconds
📚 Ссылки для тех, кто хочет поумнеть
Must-read:
- The C10K Problem — Если не читал, ты позор профессии
- Nginx Architecture — Почему Nginx быстрее Apache
- Systemd Socket Activation — Zero-downtime deployment
Research:
- Google: "Software Engineering at Google" (2020)
- Facebook: "Tales from the Trenches" — Как не ебануться с масштабом
Метрики:
- Brendan Gregg: Linux Performance
- USE Method — Utilization, Saturation, Errors
🎯 Итого: Чек-лист "Ты не полный мудак"
Для веб-сервера:
- [ ] Есть graceful shutdown (SIGTERM обрабатывается)
- [ ] Все дескрипторы закрываются (файлы, сокеты)
- [ ] Есть таймауты (на accept, read, write)
- [ ] Connection pooling настроен адекватно
- [ ] Логируются метрики (не
print, а Prometheus/Grafana) - [ ] Load testing пройден (не 10 RPS, а 1000+)
- [ ] Есть circuit breaker для внешних API
- [ ] Memory leaks проверены (valgrind/heaptrack)
Для тебя лично:
- [ ] Понимаешь разницу между blocking/non-blocking I/O
- [ ] Можешь объяснить event loop за 2 минуты
- [ ] Знаешь, что такое file descriptor leak
- [ ] Профилировал свой код хоть раз
- [ ] Читал man-страницы, а не только Stack Overflow
🔥 Заключение: Почему столько эмоций?
Потому что нынешним middle разработчикам нужно объяснять азы. Азы, Карл!
Не архитектуру микросервисов. Не Kubernetes. Не блокчейн. А ебучие основы:
- Закрывай файлы
- Обрабатывай ошибки
- Процесс живёт, пока его не убьют
И знаешь, что? Даже собака быстрее учится.
Собака за неделю понимает: нельзя срать на ковёр. Джуны за 3 года не понимают: нельзя делать open() без close().
Я устал. Устал быть няней для тех, кто считает git push --force нормальной практикой. Устал объяснять, почему "работает на моей машине" — не аргумент.
Так что вот вам последний совет: либо дрочите основы, либо идите в менеджеры. Там вашей некомпетентности никто не заметит.
P.S. Если ты дочитал до конца и не почувствовал себя идиотом хотя бы трижды — поздравляю, ты один из 3% адекватных разработчиков. Остальным 97% — учите основы, пожалуйста. А то мне ещё рано на пенсию, но я уже выгорел.
Данный текст является художественной гиперболой в образовательных целях. Автор не несёт ответственности за оскорблённые чувства. Если вы узнали себя в "Васе" — примите это как знак к самосовершенствованию, а не повод для обиды. Успехов в обучении основам Computer Science.