← Назад к вопросам
Что произойдет с потоками, если сервер попал под высокую нагрузку?
1.3 Junior🔥 201 комментариев
#DevOps и инфраструктура#Django
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что произойдёт с потоками при высокой нагрузке на сервер
Этот вопрос о деградации системы под нагрузкой. Расскажу подробно, что происходит и как это предотвратить.
1. Первый признак: увеличение latency
Сценарий: Нормальная нагрузка 100 req/sec, потом скачок до 10K req/sec
import threading
import time
from datetime import datetime
request_times = []
lock = threading.Lock()
def handle_request():
start = time.time()
# Простая обработка
time.sleep(0.01) # 10ms processing
duration = time.time() - start
with lock:
request_times.append(duration)
# При нормальной нагрузке
for _ in range(100):
t = threading.Thread(target=handle_request)
t.start()
time.sleep(1)
print(f"Средний response time: {sum(request_times)/len(request_times):.3f}s")
# Output: 0.010s (нормально)
# При высокой нагрузке (10K потоков)
request_times.clear()
threads = []
for _ in range(10000):
t = threading.Thread(target=handle_request)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Средний response time: {sum(request_times)/len(request_times):.3f}s")
# Output: 2.340s (100x медленнее!)
# Почему? Context switching overhead
2. Истощение ресурсов системы
Что именно исчерпывается:
import resource
import threading
import os
# 1. MEMORY (каждый поток занимает ~8MB)
print(f"Максимум потоков в памяти: ~{resource.getrlimit(resource.RLIMIT_NPROC)[0]//2}")
# Если создать 50K потоков:
threads = []
try:
for i in range(50000):
t = threading.Thread(target=lambda: time.sleep(100))
threads.append(t)
t.start()
except RuntimeError as e:
print(f"Ошибка: {e}")
# RuntimeError: can't start new thread
# Закончилась память или достигнут лимит потоков ОС
# 2. STACK MEMORY
# Каждый поток имеет свой stack (~1-8 MB)
# 10K потоков = 10-80 GB памяти только на стеки!
# 3. CPU (context switching overhead)
# Если потоков > ядер CPU → constant switching
# На каждый switch: 1-10 µs потеряется
num_threads = 10000 # На 8-ядерном CPU
switch_overhead_per_sec = 10000 * 0.001 # ~10 ms/sec потеряется
# 4. FILE DESCRIPTORS
print(f"Лимит файловых дескрипторов: {resource.getrlimit(resource.RLIMIT_NOFILE)[0]}")
# Типично 1024
# Если каждый поток открывает соединение с БД → быстро исчерпается
3. Каскадный отказ: проблемы в цепочке
Самая опасная ситуация:
# SCENARIO: Microservice architecture при высокой нагрузке
# Service A: API Gateway
# - Получает 10K req/sec вместо 1K
# - Создаёт 10K потоков
# - Память исчерпывается
# - Начинает отказывать
# Service B: User Service (зависит от A)
# - Ждёт ответа от A
# - Потоки накапливаются (потому что ждут)
# - Memory leak
# - Тоже падает
# Service C: Order Service (зависит от B)
# - B не отвечает
# - Потоки зависают
# - Потом падает и это
# Service D: Payment Service (критический)
# - Последний в цепи
# - ПАДАЕТ ВСЯ СИСТЕМА
from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def requests_retry_session(
retries=3,
backoff_factor=0.3,
status_forcelist=(500, 502, 504),
session=None,
):
session = session or Session()
retry = Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff_factor,
status_forcelist=status_forcelist,
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
# ❌ Неправильно: бесконечные retry'ы
while True:
try:
response = requests.get("http://slow-service.com/api")
break
except:
pass # Бесконечный loop!
# ✅ Правильно: exponential backoff + timeout
session = requests_retry_session()
try:
response = session.get(
"http://slow-service.com/api",
timeout=5 # Максимум 5 секунд!
)
except Exception:
# Fallback или ошибка
pass
4. Thread starvation
Потоки ждут, но никогда не выполняются:
from concurrent.futures import ThreadPoolExecutor
import time
# ❌ Неправильно: слишком мало рабочих потоков
with ThreadPoolExecutor(max_workers=2) as executor:
# Подаём 10K задач
futures = []
for _ in range(10000):
future = executor.submit(lambda: time.sleep(10))
futures.append(future)
# Первая задача выполняется
# Остальные 9998 ждут в очереди
# Пользователи видят timeout's
# ✅ Правильно: подобрать нужное количество потоков
from multiprocessing import cpu_count
optimal_threads = cpu_count() * 4 # Для I/O bound
with ThreadPoolExecutor(max_workers=optimal_threads) as executor:
futures = []
for _ in range(10000):
future = executor.submit(lambda: time.sleep(10))
futures.append(future)
# Задачи распределяются более равномерно
5. GIL становится узким местом
# При высокой нагрузке с CPU-bound работой
import threading
import time
def cpu_work():
result = 0
for i in range(100_000_000):
result += i
return result
# Нормальная нагрузка: 1 поток
start = time.time()
cpu_work()
print(f"1 поток: {time.time() - start:.2f}s")
# Output: 2.50s
# Высокая нагрузка: 8 потоков
start = time.time()
threads = []
for _ in range(8):
t = threading.Thread(target=cpu_work)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"8 потоков: {time.time() - start:.2f}s")
# Output: 18.50s (!!! медленнее!)
# GIL не пускает потоки параллельно
# Context switching просто убивает performance
6. Практический пример: что видит пользователь
# Нормальное состояние
# Request: 10ms
# Response: 10ms
# Total: 20ms ✅
# Начало перегрузки (100 req/sec вместо 10)
# Request: 10ms
# Queue wait: 50ms (потоки заняты)
# Response: 10ms
# Total: 70ms ⚠️
# Сильная перегрузка (1000 req/sec)
# Request: 10ms
# Queue wait: 5000ms (очень долго)
# Response: 10ms
# Total: 5000ms timeout ❌
# Критическая перегрузка (10000 req/sec)
# Server: не отвечает
# User: Connection refused ❌
7. Как предотвратить деградацию
Вариант 1: Rate Limiting
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
@app.get("/api/users")
@limiter.limit("100/minute") # Максимум 100 req/min
def get_users(request: Request):
return {"users": []}
# Клиенты которые превышают лимит получат 429 Too Many Requests
# Система остаётся стабильна
Вариант 2: Circuit Breaker
from pybreaker import CircuitBreaker
db_breaker = CircuitBreaker(
fail_max=5, # После 5 ошибок
reset_timeout=60 # Ждём 60 сек
)
@db_breaker
def query_database():
return db.execute("SELECT * FROM users")
# Если БД падает — circuit открывается
# Новые запросы немедленно отказываются (быстро)
# Вместо зависания
Вариант 3: Thread Pool Size Tuning
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import cpu_count
# Для I/O bound (web scraping, API calls)
IO_BOUND_THREADS = cpu_count() * 4
# Для CPU bound (обработка данных)
CPU_BOUND_THREADS = cpu_count()
# Для mixed
MIXED_THREADS = cpu_count() * 2
# ✅ Правильное использование
with ThreadPoolExecutor(max_workers=IO_BOUND_THREADS) as executor:
futures = [executor.submit(fetch_url, url) for url in urls]
results = [f.result(timeout=10) for f in futures] # Тайм-аут!
Вариант 4: Graceful Degradation
@app.get("/api/users/{user_id}")
def get_user(user_id: int):
try:
# Пытаемся получить полные данные
user = db.get_user_with_posts(user_id)
return {"user": user, "posts": user.posts}
except TimeoutError:
# При перегрузке возвращаем только основные данные
user = cache.get(f"user:{user_id}")
if user:
return {"user": user, "posts": []} # Без постов
raise
Вариант 5: Асинхронность вместо потоков
# ❌ Потоки — 10K потоков = 80GB памяти
with ThreadPoolExecutor(max_workers=10000) as executor:
futures = [executor.submit(async_io_task) for _ in range(10000)]
# ✅ Asyncio — 10K задач = ~100MB памяти
import asyncio
async def main():
tasks = [async_io_task() for _ in range(10000)]
await asyncio.gather(*tasks)
asyncio.run(main())
# Даже под нагрузкой:
# Asyncio: стабильно, 100MB
# Потоки: памяти нет, система падает
8. Мониторинг и алерты
import psutil
import time
def monitor_system():
while True:
# Количество потоков
thread_count = threading.active_count()
if thread_count > 1000:
alert(f"Слишком много потоков: {thread_count}")
# Использование памяти
memory_percent = psutil.virtual_memory().percent
if memory_percent > 80:
alert(f"Критический уровень памяти: {memory_percent}%")
# Нагрузка на CPU
cpu_percent = psutil.cpu_percent(interval=1)
if cpu_percent > 90:
alert(f"Критическая нагрузка CPU: {cpu_percent}%")
# Соединения с БД
db_connections = len(db.pool._holders)
if db_connections > db.pool.size * 0.9:
alert(f"Соединения с БД почти исчерпаны: {db_connections}")
time.sleep(10)
Итоговая таблица: последовательность деградации
┌──────────┬──────────────────┬──────────────┬─────────────┐
│ Stage │ Нагрузка │ Что происходит │ Решение │
├──────────┼──────────────────┼──────────────┼─────────────┤
│ 1. Normal│ 1K req/sec │ Всё быстро │ - │
│ 2. Warm │ 5K req/sec │ Latency ↑ → │ Rate limit │
│ 3. Hot │ 10K req/sec │ Memory ↑ → │ Async │
│ 4. Crit │ 50K+ req/sec │ Потоки ↑ → │ Circuit │
│ 5. Dead │ 100K+ req/sec │ OOM → crash │ Degradation │
└──────────┴──────────────────┴──────────────┴─────────────┘
Вывод
При высокой нагрузке потоки:
- Увеличивают latency (context switching)
- Исчерпывают память (8MB per thread)
- Создают bottleneck'и (GIL в Python)
- Могут зависнуть (thread starvation)
- Вызывают каскадный отказ (cascade failure)
Профилактика:
- Rate limiting
- Circuit breaker'ы
- Asyncio вместо потоков
- Graceful degradation
- Мониторинг и алерты
- Load testing перед production