← Назад к вопросам
Что делать если веб-сервис начал потреблять много памяти?
1.0 Junior🔥 51 комментариев
#DevOps и инфраструктура#Django
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Диагностика и решение проблемы утечки памяти в веб-сервисе
Утечка памяти в веб-сервисе — серьёзная проблема, которая может привести к краху приложения. Я расскажу о систематическом подходе для её решения.
Шаг 1: Подтверждение проблемы (мониторинг)
Сначала нужно подтвердить, что это именно утечка:
import psutil
import os
import time
process = psutil.Process(os.getpid())
memory_snapshots = []
for i in range(10):
memory_info = process.memory_info()
rss_mb = memory_info.rss / 1024 / 1024
memory_snapshots.append(rss_mb)
print(f"Память: {rss_mb:.2f} MB")
time.sleep(5)
if memory_snapshots[-1] > memory_snapshots[0] * 1.5:
print("ВНИМАНИЕ: Утечка памяти!")
Шаг 2: Профилирование памяти
Использую memory_profiler и tracemalloc:
from memory_profiler import profile
import tracemalloc
@profile
def process_request(data):
large_list = [i for i in range(1000000)]
return sum(large_list)
tracemalloc.start()
snapshot1 = tracemalloc.take_snapshot()
data = process_request([])
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
for stat in top_stats[:10]:
print(stat)
Шаг 3: Анализ объектов в памяти
import gc
def analyze_memory():
gc.collect()
from collections import Counter
objects = gc.get_objects()
counts = Counter(type(obj).__name__ for obj in objects)
for obj_type, count in counts.most_common(10):
print(f"{obj_type}: {count}")
analyze_memory()
Шаг 4: Проблемы в веб-приложениях
Частая ошибка — неконтролируемые кеши:
# УТЕЧКА
request_cache = {}
@app.get("/process/{id}")
def process(id: str):
result = expensive_computation()
request_cache[id] = result # Растёт навсегда!
return result
# ИСПРАВЛЕНО
from functools import lru_cache
@lru_cache(maxsize=128)
def cached_computation(key: str):
return expensive_computation()
# ИЛИ с TTL
from datetime import datetime, timedelta
class TTLCache:
def __init__(self, ttl_seconds=3600):
self.cache = {}
self.ttl = ttl_seconds
def get(self, key):
if key in self.cache:
value, timestamp = self.cache[key]
if datetime.now() - timestamp < timedelta(seconds=self.ttl):
return value
del self.cache[key]
return None
def set(self, key, value):
self.cache[key] = (value, datetime.now())
Шаг 5: Слушатели событий
Слушатели часто не удаляются:
# УТЕЧКА
event_listeners = []
def add_listener(callback):
event_listeners.append(callback)
# ИСПРАВЛЕНО
from weakref import WeakSet
class EventManager:
def __init__(self):
self._listeners = WeakSet()
def subscribe(self, callback):
self._listeners.add(callback)
def notify(self, event):
for callback in self._listeners:
if callback:
callback(event)
# ИЛИ явное управление
class EventManager:
def __init__(self):
self._listeners = {}
def subscribe(self, event_type, callback):
if event_type not in self._listeners:
self._listeners[event_type] = []
self._listeners[event_type].append(callback)
def unsubscribe(self, event_type, callback):
if event_type in self._listeners:
self._listeners[event_type].remove(callback)
Шаг 6: Потоки и асинхронные задачи
from concurrent.futures import ThreadPoolExecutor
import asyncio
# УТЕЧКА: неограниченные потоки
thread_pool = ThreadPoolExecutor()
for i in range(100000):
thread_pool.submit(heavy_task)
# ИСПРАВЛЕНО: ограничиваем
thread_pool = ThreadPoolExecutor(max_workers=10)
# Асинхронные утечки
# УТЕЧКА
async def background_work():
while True:
await asyncio.sleep(1)
# ИСПРАВЛЕНО
class BackgroundWorker:
def __init__(self):
self.task = None
async def start(self):
self.task = asyncio.create_task(self._work())
async def stop(self):
if self.task:
self.task.cancel()
try:
await self.task
except asyncio.CancelledError:
pass
async def _work(self):
while True:
try:
await asyncio.sleep(1)
except asyncio.CancelledError:
break
Шаг 7: Мониторинг в продакшене
from prometheus_client import Gauge, Counter
import gc
memory_gauge = Gauge('app_memory_mb', 'Память в МБ')
objects_gauge = Gauge('app_objects', 'Объекты')
async def monitor():
while True:
process = psutil.Process(os.getpid())
memory_mb = process.memory_info().rss / 1024 / 1024
memory_gauge.set(memory_mb)
gc.collect()
objects_gauge.set(len(gc.get_objects()))
if memory_mb > 1024:
logger.critical(f"Память {memory_mb}MB!")
await asyncio.sleep(60)
Чеклист поиска утечек
- Глобальные переменные — растут ли в цикле?
- Кеши — есть ли лимит размера?
- Слушатели — удаляются ли?
- Потоки — отменяются ли?
- Задачи — управляются ли?
- Циклические ссылки — обрабатываются ли?
- Библиотеки — имеют ли утечки?
Вывод: утечка обычно вызвана неконтролируемым ростом кешей, слушателей или задач. Профилирование быстро выявит проблему.