Introduction
Memory leaks in Python can silently degrade performance, especially in long‑running production services. This guide shows you why they happen, how to detect them, and step‑by‑step fixes you can roll out safely.
1. Python’s Memory Model
- Reference counting – the primary garbage collector.
- Cyclic GC – cleans up reference cycles.
-
Object pools – e.g.,
list,dictreuse memory.
Understanding these mechanisms helps you know where leaks can hide.
2. Common Sources of Leaks in Production
-
Unreleased file/DB handles – forgetting
close(). -
Large caches without eviction – e.g.,
lru_cache(maxsize=None). - C extensions – leaking native memory.
- Global mutable state – accumulating data in module‑level lists.
- Third‑party libraries – bugs in older versions.
3. Detecting Leaks Locally
3.1 Using tracemalloc
import tracemalloc
tracemalloc.start()
# Run the code you suspect
snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics('lineno')[:10]:
print(stat)
3.2 objgraph for object graphs
import objgraph
objgraph.show_most_common_types(limit=5)
objgraph.show_backrefs([my_obj], max_depth=3)
3.3 guppy/heapy for heap analysis
from guppy import hpy
h = hpy()
print(h.heap())
4. Step‑by‑Step Fix Workflow
- Reproduce the leak in a staging environment with a realistic workload.
-
Capture a baseline memory snapshot (
tracemallocorpsutil.Process().memory_info()). - Identify the hot objects – look for unexpected growth in type counts.
-
Trace the allocation path – use
objgraphback‑references. - Patch the code – close resources, add cache eviction, or replace the problematic library.
- Validate – run the workload again and compare memory footprints.
- Deploy – roll out the fix via CI/CD, monitoring the process’s RSS/USS.
5. Real‑World Example: Leaking a Global Cache
# buggy version
cache = {}
def get_user(id):
if id not in cache:
cache[id] = fetch_from_db(id) # never evicted
return cache[id]
Fix
from functools import lru_cache
@lru_cache(maxsize=1024) # automatic eviction
def get_user(id):
return fetch_from_db(id)
The lru_cache limits memory use and removes the manual global dict.
6. Deploy‑Time Safeguards
-
Health checks that report memory usage (
/metricswith Prometheus). - Alerting when RSS exceeds a threshold (e.g., 80% of container limit).
-
Automatic restarts – Kubernetes
livenessProbecoupled with a memory‑aware policy.
7. Continuous Monitoring
import psutil, time
while True:
mem = psutil.Process().memory_info().rss / (1024**2)
print(f"Current RSS: {mem:.2f} MB")
time.sleep(30)
Log these metrics to your observability stack and watch for trends.
8. Ready‑to‑Use Patch Toolkit
If you prefer a pre‑built solution, you can Download the pre‑configured script here. It bundles tracemalloc wrappers, automatic heap snapshots, and a tiny HTTP endpoint for on‑demand diagnostics.
Alternatively, grab the full package with Get the complete patch tool or explore the source via Access the full repository fix.
Conclusion
Fixing Python memory leaks in production is a systematic process: understand the allocator, detect the leak with the right tooling, apply targeted code changes, and monitor continuously. With the steps above and the optional toolkit, you can keep your services lean and responsive.
Top comments (0)