Memory Usage Optimization

uv is fast because it does everything in parallel and keeps massive dependency graphs in RAM. Great for speed, terrible when it devours 8GB trying to resolve your Django app's dependencies.

I watched uv eat 8GB of RAM trying to upgrade a Django project with 200-something packages. The Docker container got OOMKilled halfway through dependency resolution. Fun times at 2am.

uv benchmark results showing performance comparison

The Root Cause: Aggressive Parallelism

uv opens 50 concurrent downloads by default via UV_CONCURRENT_DOWNLOADS. Every thread hoards its own buffers and dependency trees. Do the math: 50 threads × bloated metadata = your RAM is fucked.

The official benchmarks don't show this nightmare because they test tiny individual packages, not your 500-dependency monorepo with circular imports and conflicting versions everywhere.

What to expect:

  • Small projects (< 20 packages): Maybe 500MB, usually fine
  • Medium projects (50-100 packages): 1-3GB, start watching your RAM
  • Large monorepos (200+ packages): 6GB+ and climbing fast

Corporate Network Reality

Corporate networks are where uv's parallel downloads go to die. Your company's proxy and firewall weren't designed for some Rust tool hammering 50 connections at once.

How your build fails:

  1. uv opens 50 connections to PyPI like a maniac
  2. Corporate firewall thinks you're under DDoS attack
  3. Half the connections timeout randomly
  4. uv retries everything, making even more connections
  5. Your build takes longer than pip would have

Classic enterprise bullshit where the "performance tool" performs worse than the thing it replaced.

Memory Optimization Strategy

Here's the configuration that actually works in constrained environments:

## Limit concurrent operations to prevent memory spikes
export UV_CONCURRENT_DOWNLOADS=4
export UV_CONCURRENT_INSTALLS=2
export UV_CONCURRENT_BUILDS=1

## Enable memory usage monitoring
export UV_VERBOSE=1

## Cache aggressively but limit cache size
export UV_CACHE_DIR=/tmp/uv-cache
export UV_LINK_MODE=copy

4 concurrent downloads typically provides 80% of the speed benefit with significantly reduced memory usage. These values balance performance with memory consumption based on testing across multiple production environments.

Corporate Network Optimization

## Corporate-friendly download strategy
export UV_CONCURRENT_DOWNLOADS=2
export UV_HTTP_TIMEOUT=60
export UV_RETRIES=3

## Work with corporate proxies instead of fighting them
export UV_EXTRA_INDEX_URL=\"https://your-nexus.company.com/simple/\"
export UV_TRUSTED_HOSTS=\"your-nexus.company.com\"

2 concurrent downloads often outperform higher concurrency in corporate environments due to rate limiting and proxy connection pool constraints.

Corporate networks typically perform better with limited concurrent connections due to infrastructure limitations.

Build Stage Memory Management

Memory Management

For Docker builds, memory staging is critical:

## Build stage: High memory limits for dependency resolution
FROM python:3.12-slim AS deps
ENV UV_CONCURRENT_DOWNLOADS=8 \
    UV_CONCURRENT_INSTALLS=4

## Runtime stage: Minimal memory footprint
FROM python:3.12-slim AS runtime
ENV UV_CONCURRENT_DOWNLOADS=1

The build vs. runtime split lets you optimize for different constraints. Build containers can use more memory for speed, runtime containers optimize for efficiency.

When uv is Actually Slower

Yeah, sometimes pip wins. Here's when:

  • Corporate networks - pip's boring serial downloads actually work
  • Tiny Docker containers - uv's parallelism triggers swapping and everything dies
  • Small projects - uv's startup overhead for installing 3 packages is overkill
  • Flaky package sources - uv's retry logic creates retry storms that crash everything

The performance comparison data admits this but hides it in the methodology footnotes.

Essential Performance Resources

For deeper optimization, these resources provide real-world insights:

Performance Troubleshooting FAQ

Q

Why is uv using 8GB of RAM to install 50 packages?

A

Because uv opens 50 parallel downloads like a maniac, and each thread hoards memory like a dragon. Complex projects = memory explosion. Fix it: export UV_CONCURRENT_DOWNLOADS=4 cuts memory usage by ~75%. I learned this after watching uv OOMKill my Django container at 3am.

Q

uv is slower than pip on our corporate network. Why?

A

Corporate firewalls think uv is a DDoS attack because it opens 50 connections at once.

Your proxy server shits itself and rate limits everything. Fix it: UV_CONCURRENT_DOWNLOADS=2

  • sometimes less parallelism is faster when your network infrastructure sucks.
Q

Why does `uv sync` take 10 minutes for a lockfile that should be instant?

A

Lockfile sync should be instant, but sometimes uv decides to re-download everything for no fucking reason. Debug this bullshit: UV_VERBOSE=1 uv sync to see what's broken. Usually it's corrupted cache (rm -rf ~/.cache/uv) or your network is garbage.

Q

My Docker builds randomly fail with "No space left on device"

A

uv hoards cache files like a packrat

  • 10GB+ easy.

Docker containers run out of space and your build dies. Fix it: UV_CACHE_DIR=/tmp/uv-cache in Docker builds. Also check that you're not copying your entire project directory including .git into the build context like an idiot.

Q

Why does dependency resolution take 5 minutes on a fast machine?

A

Complex dependency trees with conflicting version constraints trigger uv's backtracking resolver. Unlike pip, uv tries to find optimal solutions instead of just taking the first thing that works. Common in: Projects with 100+ packages and loose version constraints. Solution: Pin more versions in your lockfile or use --resolution=lowest-direct for faster resolution with potentially less optimal dependency versions.

Q

uv crashes with "Too many open files" during large installs

A

Linux file descriptor limits hit uv's parallel operations. Each download thread needs multiple file handles for sockets, temporary files, and cache operations. Temporary fix: ulimit -n 4096 before running uv. Permanent fix: Add ulimit configuration to shell profile or Docker container configuration.

Q

Performance is great locally but terrible in CI

A

CI environments have different constraints

  • usually less memory, different network configurations, and cold caches.

Your local machine has warm caches and better networking. CI optimization: Use persistent cache directories, limit concurrent operations (UV_CONCURRENT_DOWNLOADS=2), and pre-warm caches in your base images.

Q

Why does installing NumPy take forever when uv is supposed to be fast?

A

Packages with C extensions need compilation, which is CPU-bound, not network-bound. uv can't magic away GCC compilation times. Note: uv's speed improvements primarily benefit network operations and dependency resolution. Wheel compilation remains CPU-bound. Use pre-built wheels or a build cache for packages requiring compilation.

Q

My monorepo workspace resolution is ridiculously slow

A

Workspace dependency resolution scales poorly with the number of packages. uv has to solve the entire workspace graph simultaneously, which can be exponential complexity. Mitigation: Break large workspaces into smaller ones, or use --no-workspace to opt specific packages out of workspace resolution when possible.

Q

uv is eating network bandwidth and our admin is pissed

A

Default configuration opens 50+ connections and downloads at maximum speed. On shared corporate networks, this can saturate bandwidth or trigger abuse detection. Solution: Use UV_CONCURRENT_DOWNLOADS=3 and consider UV_HTTP_TIMEOUT=30 to reduce network impact on shared infrastructure.

Q

Cache hits are low despite identical dependency lists

A

uv's cache keys include more metadata than you might expect

  • Python version, platform tags, package indices, and more.

Small environment differences can cause cache misses. Debug cache efficiency: Check uv cache info and look for cache directory bloat. If cache size is growing without reuse, you have a cache key problem.

Cache Optimization for Production Performance

Cache Performance

uv's performance relies heavily on cache efficiency. Effective caching enables near-instant installs, while cache misses result in full downloads and builds. Based on production experience managing uv caches across multiple teams, these strategies provide measurable performance improvements.

Cache Architecture Reality

uv maintains a global cache structure that's more complex than most people realize. It's not just "download packages and store them." There are separate caches for:

uv maintains separate cache stores for different package types and metadata:

  • Downloaded wheels and tarballs - the obvious stuff
  • Built wheels from source - compiled C extensions
  • Package metadata - dependency information and version data
  • Resolved dependency trees - the expensive computation results
  • Git repository clones - for packages installed from Git

Each cache type has different invalidation rules and storage patterns. Most performance problems trace back to cache misses in the dependency tree resolution cache.

Cache Location Strategy

Default location problems:

## Default: ~/.cache/uv (Linux/macOS)
## Issues: Hidden directory, hard to monitor, survives user changes

Production-optimized locations:

## Development environments
export UV_CACHE_DIR="$HOME/.uv-cache"  # Visible for monitoring

## CI/CD environments
export UV_CACHE_DIR="/tmp/uv-cache"    # Fast SSD, auto-cleanup

## Docker builds
export UV_CACHE_DIR="/cache/uv"        # Persistent mount point

Cache location significantly impacts performance. Local SSD storage can provide 5x performance improvements compared to NFS-mounted home directories.

Cache Size Management

uv caches grow without bounds by default. A main monorepo cache reached 40-50GB after 6 months, causing slow filesystem operations and impacting backup processes.

Size monitoring that works:

## Check current cache usage
uv cache info

## Cache size breakdown by type
du -sh ~/.cache/uv/{archive,built,git,simple}

## Automated cleanup (add to cron)
uv cache clean --older-than 30d

Strategic cache pruning:

## Keep wheels, clear metadata (forces fresh dependency resolution)
uv cache clean --package-type metadata

## Clear only source-built packages (keep pre-built wheels)
uv cache clean --package-type built

## Nuclear option: start fresh
uv cache clean --all

Weekly cache cleanup jobs targeting metadata caches specifically force dependency resolution to use current data while preserving expensive wheel builds.

Cache Key Invalidation Gotchas

uv's cache keys are more sensitive than expected. These seemingly minor changes invalidate caches:

  • Python version changes (3.11.1 → 3.11.2) - complete cache miss
  • Index URL ordering - different PyPI mirrors = different cache keys
  • Environment variables - some env vars are included in cache hashing
  • Platform tags - same code, different Docker base image = cache miss

Cache-friendly development practices:

## Pin Python versions precisely
export UV_PYTHON=python3.11.5

## Consistent index ordering
export UV_EXTRA_INDEX_URL="https://pypi.org/simple/,https://internal.company.com/simple/"

## Minimize environment variable differences between environments

Multi-Environment Cache Strategy

Running uv across development, CI, and production environments with shared caches requires coordination:

Centralized cache architecture:

## Shared read-only cache for CI runners
export UV_CACHE_DIR="/shared/cache/uv"
export UV_LINK_MODE=copy  # Don't modify shared cache

## Environment-specific overlay caches
export UV_LOCAL_CACHE_DIR="/tmp/uv-local"

Cache warming for CI:

## Pre-populate CI caches during off-peak hours
uv sync --cache-only  # Download without installing
uv pip compile requirements.in --cache-only  # Pre-resolve dependencies

This pattern reduced CI build times from 8 minutes to under 2 minutes by ensuring cache hits on most builds.

Performance Monitoring That Matters

Most people monitor build times but miss cache efficiency. These metrics reveal actual performance:

## Cache hit ratio measurement
UV_VERBOSE=1 uv sync 2>&1 | grep -c "Using cached"
UV_VERBOSE=1 uv sync 2>&1 | grep -c "Downloading"

## Cache size growth tracking
du -sb ~/.cache/uv | awk '{print $1}' >> /tmp/cache-size.log

## Resolution time measurement (the expensive part)
time uv pip compile requirements.in --resolver-only

Target performance metrics:

  • Cache hit ratio: 80%+ for stable projects
  • Resolution time: Under 10 seconds for large projects
  • Cache growth: Linear progression with new dependencies

When Cache Optimization Doesn't Help

Cache efficiency has limits. These scenarios benefit from other optimizations:

  • Fresh environments - first build is always slow, focus on reducing cold start impact
  • Rapidly changing dependencies - cache churn negates benefits
  • Source-only packages - compilation time dominates, use pre-built wheels or build servers
  • Network-constrained environments - bandwidth is the bottleneck, not cache

Cache Debugging Commands

When performance is bad, systematic cache analysis helps:

## Detailed cache inspection
uv cache info --verbose

## Find cache misses for specific packages
UV_VERBOSE=1 uv pip install numpy 2>&1 | grep -E "(cache|download)"

## Measure resolution vs download time
time uv pip compile requirements.in --no-deps  # Resolution only
time uv sync --offline  # Installation from cache only

The most common issue: metadata cache misses causing full dependency re-resolution. This looks like network activity but it's actually computational overhead.

Essential Cache Resources

For advanced cache optimization and troubleshooting:

uv Performance Analysis by Scenario

Scenario

uv Result

pip Result

Reality Check

Fresh install, large project

Fast but high memory usage

Slow but reliable

uv requires adequate RAM (8GB+)

Cached install

Significantly faster

Slow

uv provides major advantage

Corporate proxy environments

Often slower than pip

Reliable but slow

Requires concurrency tuning

Memory-constrained CI

May fail with OOM

Reliable

pip preferable without memory optimization

Production Performance Monitoring

Performance Monitoring

Effective optimization requires measurement. Production experience with uv across multiple teams demonstrates that build time alone is insufficient. Performance analysis must include cache efficiency, memory usage patterns, and failure recovery times.

This monitoring approach provides actionable insights without adding overhead that negates performance gains.

Key performance metrics to track in production environments:

The Metrics That Matter

Cache hit ratio trumps build speed. A 5-minute build with 90% cache hits is better than a 3-minute build with 40% cache hits. The second scenario indicates you'll have unpredictable build times as your project grows.

## Measure cache efficiency (add to CI)
cache_hits=$(UV_VERBOSE=1 uv sync 2>&1 | grep -c "Using cached")
cache_misses=$(UV_VERBOSE=1 uv sync 2>&1 | grep -c "Downloading")
efficiency=$((cache_hits * 100 / (cache_hits + cache_misses)))
echo "Cache efficiency: ${efficiency}%"

Memory peak vs sustained usage. uv spikes memory during dependency resolution, then drops to normal levels. Monitor both peaks (for capacity planning) and sustained usage (for cost optimization).

## Memory monitoring during builds
/usr/bin/time -v uv sync 2>&1 | grep "Maximum resident set size"
## Track this metric over time to detect memory leaks or cache bloat

Network request patterns. Count requests, not just bandwidth. Corporate firewalls care more about connection count than total bytes transferred.

## Network request tracking
strace -e trace=network uv sync 2>&1 | grep connect | wc -l
## Good: < 100 connections for typical projects
## Problem: > 500 connections (triggers corporate rate limiting)

Production Monitoring Strategy

Continuous measurement without overhead:

#!/bin/bash
## build-monitor.sh - Add to CI/CD pipeline
start_time=$(date +%s)
start_mem=$(ps -o rss= -p $$)

UV_VERBOSE=1 uv sync > build.log 2>&1
exit_code=$?

end_time=$(date +%s)
build_duration=$((end_time - start_time))
cache_hits=$(grep -c "Using cached" build.log)
cache_misses=$(grep -c "Downloading" build.log)
peak_mem=$(grep "Maximum resident set size" build.log | awk '{print $6}')

## Send metrics to your monitoring system
curl -X POST "https://metrics.company.com/uv-performance" \
  -d "duration=${build_duration}" \
  -d "cache_hits=${cache_hits}" \
  -d "cache_misses=${cache_misses}" \
  -d "peak_memory=${peak_mem}" \
  -d "exit_code=${exit_code}"

Key insight: Measure every build but alert only on trends, not individual failures. uv performance varies significantly based on network conditions and concurrent builds.

Performance Regression Detection

Dependency resolution time trending upward signals growing project complexity or cache invalidation issues:

## Track resolution time separately from download time
time uv pip compile requirements.in --dry-run  # Resolution only
time uv sync --offline  # Installation from cache only

Cache size growing faster than project growth indicates cache key problems or cleanup failures:

## Weekly cache health check
du -sb ~/.cache/uv
find ~/.cache/uv -name "*.whl" -mtime +30 | wc -l  # Old wheels
find ~/.cache/uv -name "*.metadata" -mtime +7 | wc -l  # Stale metadata

Memory usage patterns changing suggests configuration drift or dependency hell:

## Memory usage profiling for large projects
/usr/bin/time -f "Peak RSS: %M KB, Time: %E" uv sync
## Track this weekly - significant changes indicate problems

Failure Analysis and Recovery

uv failures fall into patterns. Systematic logging helps identify root causes:

## Failure categorization
if uv sync 2>&1 | grep -q "ConnectionError"; then
    echo "NETWORK_FAILURE: $(date)" >> /tmp/uv-failures.log
elif uv sync 2>&1 | grep -q "MemoryError\|killed"; then
    echo "MEMORY_FAILURE: $(date)" >> /tmp/uv-failures.log
elif uv sync 2>&1 | grep -q "No such file"; then
    echo "CACHE_CORRUPTION: $(date)" >> /tmp/uv-failures.log
fi

Recovery time matters more than failure rate. Network hiccups happen; what matters is how quickly builds recover:

## Automated recovery with exponential backoff
attempt=1
while ! uv sync; do
    if [ $attempt -gt 3 ]; then
        echo "FAILED after 3 attempts, escalating"
        exit 1
    fi
    wait_time=$((2 ** attempt))
    echo "Attempt $attempt failed, waiting ${wait_time}s"
    sleep $wait_time
    attempt=$((attempt + 1))
done

Environment-Specific Optimization

Development environment monitoring:

## Developer productivity metrics
echo "$(date): Build started" >> ~/.uv-timings
uv sync
echo "$(date): Build completed" >> ~/.uv-timings
## Track how often developers wait for builds

CI/CD environment optimization:

## CI performance dashboard data
export CI_BUILD_START=$(date +%s)
uv sync
export CI_BUILD_END=$(date +%s)
export CI_BUILD_DURATION=$((CI_BUILD_END - CI_BUILD_START))

## Submit to CI metrics
echo "uv_build_duration_seconds=${CI_BUILD_DURATION}" >> $GITHUB_STEP_SUMMARY

Production deployment tracking:

## Production deployment performance
deployment_start=$(date +%s)
uv sync --locked --no-dev
deployment_end=$(date +%s)
echo "Production deployment: $((deployment_end - deployment_start))s" | \
  tee -a /var/log/deployment-metrics.log

Performance Alerting Strategy

Alert on trends, not individual events:

  • Build time increase > 50% over 7-day average - investigate dependency changes
  • Cache hit ratio < 70% for 3 consecutive builds - cache invalidation problem
  • Memory usage > 150% of historical average - potential memory leak
  • Network error rate > 10% over 24 hours - infrastructure issue

Don't alert on:

  • Single build failures (network hiccups happen)
  • First build of the day being slow (cold cache expected)
  • Build time variations < 30% (normal network variance)

Advanced Performance Profiling

When standard metrics aren't enough:

## CPU profiling for slow resolution
perf record -g uv pip compile requirements.in
perf report  # Identify bottlenecks in dependency resolution

## I/O profiling for cache performance
iotop -a -o -d 1 &
uv sync
## Look for excessive disk I/O during cache operations

## Network profiling for corporate environments
tcpdump -i any -w uv-network.pcap &
uv sync
## Analyze connection patterns and timeouts

Memory profiling for large projects:

## Track memory allocation patterns
valgrind --tool=massif uv sync
ms_print massif.out.* | head -50
## Identify memory allocation hotspots

Production Optimization Examples

Case study: Build time optimization results

A Django monorepo with 12-minute builds was optimized through systematic measurement:

  1. Cache location - moved from NFS to local SSD: reduced by 2-3 minutes
  2. Concurrent downloads - reduced from 50 to 6: saved 2 minutes by eliminating timeouts
  3. Cache warming - pre-populated common dependencies: saved 2-3 minutes

Total improvement: 12 minutes → 6-7 minutes

Key finding: Reducing concurrency improved performance because the corporate network couldn't reliably handle 50 simultaneous connections.

Essential Performance Resources

For deep performance analysis and optimization: