← Назад к вопросам

Что делать если веб-сервис начал зависать?

1.7 Middle🔥 101 комментариев
#Django#Git и VCS#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Диагностика и устранение зависаний веб-сервиса

В production это критическая ситуация. За 10+ лет разработал структурированный подход для быстрого восстановления сервиса.

Первые 5 минут: Стабилизация

Шаг 1: Определить масштаб проблемы

# Вопросы:
problems = {
    "scope": "Зависает весь сервис или конкретный endpoint?",
    "users_affected": "Все пользователи или избранные?",
    "duration": "Первый раз или продолжается долго?",
    "traffic": "Была ли spike в трафике?",
    "recent_changes": "Были ли деплои в последние часы?"
}

Шаг 2: Быстрые действия

# 1. Проверить статус сервиса
curl -s http://localhost:8000/health | jq .

# 2. Посмотреть логи (последние 100 строк)
tail -n 100 /var/log/app.log

# 3. Проверить процесс
ps aux | grep python
top -p <PID>

# 4. Проверить БД соединения
# SELECT * FROM pg_stat_activity;
# Есть ли долгие транзакции?

# 5. Если совсем плохо - graceful restart
sudo systemctl restart app-service

Вторая фаза: Диагностика (5-15 минут)

Инструменты для анализа

# Инструмент 1: Профилирование памяти
import tracemalloc

tracemalloc.start()
# ... код ...
current, peak = tracemalloc.get_traced_memory()
print(f"Current: {current / 10**6}MB; Peak: {peak / 10**6}MB")

# Инструмент 2: Профилирование CPU
import cProfile
import pstats

pr = cProfile.Profile()
pr.enable()
# ... критичный код ...
pr.disable()
ps = pstats.Stats(pr)
ps.sort_stats('cumulative')
ps.print_stats(10)  # Top 10

# Инструмент 3: Проверка threads
import threading
for thread in threading.enumerate():
    print(f"Thread: {thread.name}, daemon={thread.daemon}")

Частые причины зависания

# 1. Утечка памяти (Memory Leak)
problems = []
while True:
    problem = {"data": list(range(1000000))}
    problems.append(problem)  # Никогда не очищается!

# Решение: Использовать context manager, cleanup в finally

# 2. Блокирующие операции в async коде
async def bad_endpoint():
    time.sleep(10)  # ПЛОХО! Блокирует весь event loop
    return "ok"

# Хорошо: используй async функции
async def good_endpoint():
    await asyncio.sleep(10)  # OK, не блокирует
    return "ok"

# 3. Deadlock в БД
# Transaction 1: SELECT * FROM users WHERE id=1 FOR UPDATE
# Transaction 2: SELECT * FROM orders WHERE user_id=1 FOR UPDATE
# Потом пытаются поменяться местами - deadlock

# 4. Бесконечный цикл или рекурсия
def bad_recursion(n):
    return bad_recursion(n+1)  # Бесконечная рекурсия!

# 5. Slow query в БД
# SELECT * FROM huge_table WHERE status='pending'  # Нет индекса!

Диагностика систем

Для Django приложения

# 1. Проверить количество БД соединений
from django.db import connections
for conn in connections.all():
    print(f"Connection: {conn.alias}, in_atomic={conn.in_atomic_block}")

# 2. Включить SQL логирование
from django.db import connection
from django.test.utils import CaptureQueriesContext

with CaptureQueriesContext(connection) as context:
    # ... код ...
    for query in context:
        print(f"{query['time']:.2f}s: {query['sql']}")

# 3. Найти медленные queries
from django.db import connection
from django.db.backends import connection

slow_queries = [
    q for q in connection.queries 
    if float(q['time']) > 1.0
]

Для FastAPI приложения

# 1. Проверить активные endpoints
from fastapi import Request
from datetime import datetime

active_requests = {}

@app.middleware("http")
async def track_requests(request: Request, call_next):
    request_id = str(datetime.now())
    active_requests[request_id] = f"{request.method} {request.url.path}"
    try:
        response = await call_next(request)
    finally:
        del active_requests[request_id]
    return response

# 2. Найти медленный endpoint
import time
from starlette.middleware.timing import TimingMiddleware

@app.middleware("http")
async def log_timing(request: Request, call_next):
    start = time.time()
    response = await call_next(request)
    elapsed = time.time() - start
    if elapsed > 1.0:  # Если больше секунды
        print(f"SLOW: {request.method} {request.url.path} took {elapsed:.2f}s")
    return response

Стратегия восстановления

Если memory leak

# 1. Немедленно: Ограничить использование памяти
# В контейнер: memory limit = 2GB (по умолчанию unlimited)

# 2. Автоматический restart при превышении
# systemd service с MemoryMax
[Service]
MemoryMax=2G
MemoryAccounting=true

# 3.롱 term: Найти leak с помощью profiler
# python -m memory_profiler script.py
# или guppy3 для heap анализа

Если slow query

-- 1. Найти медленный query
SELECT query, mean_time, stddev_time 
FROM pg_stat_statements 
ORDER BY mean_time DESC 
LIMIT 10;

-- 2. Проверить план
EXPLAIN ANALYZE
SELECT * FROM orders WHERE status = 'pending';

-- 3. Добавить индекс
CREATE INDEX idx_orders_status ON orders(status);

-- 4. Переписать query если нужно
-- Плохо: SELECT * (берёт ненужные колонны)
-- Хорошо: SELECT id, user_id, total (только нужное)

Если deadlock в БД

# В Django ORM:
from django.db import transaction

# Используй select_for_update
with transaction.atomic():
    user = User.objects.select_for_update().get(id=1)
    # Заблокируем строку, никто другой не может её менять
    user.balance -= 100
    user.save()

# В PostgreSQL: использовать SKIP LOCKED
SELECT * FROM tasks 
WHERE status = 'pending' 
FOR UPDATE SKIP LOCKED  -- Пропустить заблокированные
LIMIT 1;

Prevention: Как избежать зависаний

# 1. Мониторинг
from prometheus_client import Counter, Histogram

request_duration = Histogram('request_duration_seconds', 'Request duration')
active_requests = Gauge('active_requests', 'Active requests')

# 2. Rate limiting
from slowapi import Limiter
limiter = Limiter(key_func=get_remote_address)

@app.get("/api/data")
@limiter.limit("100/minute")
async def get_data():
    return {"data": "value"}

# 3. Timeouts
import signal

def timeout_handler(signum, frame):
    raise TimeoutError()

signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(30)  # 30 секунд

try:
    result = slow_function()
finally:
    signal.alarm(0)  # Отменить таймаут

# 4. Circuit breaker для внешних сервисов
from pybreaker import CircuitBreaker

db_breaker = CircuitBreaker(fail_max=5, reset_timeout=60)

@db_breaker
def call_external_service():
    return requests.get("http://slow-service/api")

Чеклист восстановления

  • Проверить последние логи
  • Проверить memory и CPU usage
  • Проверить активные БД connections
  • Проверить recent deployments
  • Проверить медленные queries
  • Проверить deadlocks
  • Graceful restart если нужно
  • Включить мониторинг/alerting
  • Post-mortem анализ

Главное

Зависание — это симптом. Нужно найти причину (memory, CPU, lock), а не просто перезагружать сервис.