DEV Community

Deep Fix
Deep Fix

Posted on

Fix Python Memory Leaks in Production – Debugging, Profiling, and Prevention

Introduction

Memory leaks in long‑running Python services can silently degrade performance and eventually crash your production environment. In this guide we’ll walk through a systematic, production‑ready approach to detect, diagnose, and fix Python memory leaks.


1. Reproduce the Leak in a Controlled Environment

  1. Isolate the suspect module – spin up a minimal script that imports the module and runs the problematic workload.
  2. Run the script repeatedly (e.g., in a loop) to let the leak surface.
  3. Capture baseline memory usage with psutil or the OS top/htop.
import psutil, os, time
process = psutil.Process(os.getpid())
for i in range(1000):
    # call the function that may leak
    suspect_function()
    if i % 100 == 0:
        print(f"Iteration {i}: {process.memory_info().rss / 1e6:.2f} MB")
        time.sleep(0.1)
Enter fullscreen mode Exit fullscreen mode

If memory grows linearly, you have a leak.


2. Pinpoint the Leak with Profiling Tools

2.1 tracemalloc

tracemalloc tracks memory allocations by source line.

import tracemalloc
tracemalloc.start()
# run workload
suspect_function()
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
    print(stat)
Enter fullscreen mode Exit fullscreen mode

2.2 objgraph

Visualize object graphs to see unexpected reference cycles.

import objgraph
objgraph.show_backrefs(
    objgraph.by_type('MyLeakyClass')[0],
    max_depth=5,
    filename='leak.png'
)
Enter fullscreen mode Exit fullscreen mode

2.3 memory_profiler

Line‑by‑line memory usage with the @profile decorator.

from memory_profiler import profile

@profile
def suspect_function():
    # existing code
    ...
Enter fullscreen mode Exit fullscreen mode

Run with python -m memory_profiler script.py.


3. Common Leak Patterns & Fixes

Pattern Typical Symptom Fix
Unclosed file/DB handles FD count climbs, memory stays high Use with statements or explicit .close()
Large caches without eviction Cache size grows indefinitely Implement functools.lru_cache(maxsize=…) or manual eviction
Reference cycles involving objects with __del__ GC can't collect Break cycles manually or avoid __del__
Global mutable defaults State leaks across calls Replace with None and init inside function

4. Apply the Fix in Production

  1. Write a regression test that runs the workload for a fixed number of iterations and asserts that memory growth stays within a tolerance (e.g., <5 %).
  2. Deploy behind a feature flag so you can roll back instantly if something goes wrong.
  3. Monitor after release – add a Prometheus gauge that exports process_resident_memory_bytes.
# prometheus.yml snippet
- job_name: 'python_app'
  static_configs:
    - targets: ['localhost:8000']
Enter fullscreen mode Exit fullscreen mode

5. Automation & Continuous Guardrails

  • Integrate memory_profiler into your CI pipeline with a threshold.
  • Use pytest fixtures that start tracemalloc and fail the test if the snapshot delta exceeds a limit.
import pytest, tracemalloc

@pytest.fixture(autouse=True)
def watch_memory():
    tracemalloc.start()
    yield
    snapshot = tracemalloc.take_snapshot()
    top = snapshot.statistics('lineno')
    assert top[0].size < 5 * 1024 * 1024, "Memory spike detected"
Enter fullscreen mode Exit fullscreen mode

6. Ready‑to‑Use Patch Toolkit

If you’d like a starter kit that bundles the profiling snippets, CI guards, and a Docker‑ready environment, Download the pre‑configured script here. For a full‑featured solution, Get the complete patch tool or Access the full repository fix – all hosted at the same location.

Download the pre‑configured script here
Get the complete patch tool
Access the full repository fix


Conclusion

Fixing Python memory leaks in production is a repeatable process: reproduce, profile, pinpoint, fix, test, and monitor. By embedding these steps into your development workflow you’ll keep your services lean, responsive, and reliable.

Top comments (0)