Python
CPU usage
while loop
Docker
performance optimization

Python service uses 100 of CPU on while loop with sleep inside docker container

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

A Python service can still show very high CPU in Docker even when the loop includes sleep. This usually happens because the loop is still polling too frequently or because container CPU quota makes moderate usage appear as 100 percent. Durable fixes replace busy polling with blocking primitives and validate behavior under real cgroup limits.

Core Sections

1. Why sleep does not automatically solve busy loops

This pattern still wakes frequently and burns scheduler cycles:

python
1import time
2
3while True:
4    check_for_work()
5    time.sleep(0.001)

A one-millisecond sleep means one thousand wake-ups per second. If check_for_work is cheap but always called, CPU remains high when idle.

2. Container CPU metrics can be misleading

Inside Docker, 100 percent may represent full usage of assigned quota, not full host CPU.

If container is limited to one core, using that one core appears as 100 percent. On a large host, this can still be a small fraction of total machine capacity.

Check cgroup settings:

bash
1docker stats
2cat /sys/fs/cgroup/cpu.max 2>/dev/null || true
3cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us 2>/dev/null || true
4cat /sys/fs/cgroup/cpu/cpu.cfs_period_us 2>/dev/null || true

Interpret metrics relative to configured limits.

3. Replace polling with blocking queue operations

If work arrives through queue semantics, block on queue reads instead of repeatedly checking state.

python
1import queue
2import threading
3
4work_q = queue.Queue()
5stop_event = threading.Event()
6
7
8def process(item):
9    pass
10
11
12def worker():
13    while not stop_event.is_set():
14        try:
15            item = work_q.get(timeout=1.0)
16        except queue.Empty:
17            continue
18        try:
19            process(item)
20        finally:
21            work_q.task_done()

This drastically reduces idle CPU usage.

4. Use Event.wait for state-change waiting

For non-queue workflows, waiting on synchronization events is usually better than periodic checks.

python
1import threading
2
3ready = threading.Event()
4stop = threading.Event()
5
6
7def consumer_loop():
8    while not stop.is_set():
9        triggered = ready.wait(timeout=5.0)
10        if not triggered:
11            continue
12        handle_data()
13        ready.clear()
14
15
16def handle_data():
17    pass

The thread sleeps efficiently until real work appears.

5. Async services should await IO, not poll

Network-heavy services often perform better with asyncio await semantics.

python
1import asyncio
2
3
4async def get_work_item():
5    await asyncio.sleep(1.0)
6    return "job"
7
8
9async def process(item):
10    await asyncio.sleep(0.01)
11
12
13async def main():
14    while True:
15        item = await get_work_item()
16        await process(item)
17
18
19asyncio.run(main())

This model avoids manual loop polling and integrates naturally with async IO stacks.

6. Add profiling before and after fixes

Do not optimize blindly. Capture baseline and compare after changes.

Useful tools:

  • 'py-spy top for live Python stack sampling'
  • 'cProfile for function-level hotspots'
  • Docker metrics under fixed workload

Separate idle profile from peak workload profile. Some fixes only help idle behavior.

7. Handle graceful shutdown correctly

Busy loops often ignore signals, causing abrupt container termination and possible data loss. Use signal handlers that set stop flags.

python
1import signal
2
3
4def handle_sigterm(signum, frame):
5    stop_event.set()
6
7
8signal.signal(signal.SIGTERM, handle_sigterm)

Graceful shutdown improves reliability and reduces restart instability.

8. Logging volume can amplify CPU use

High-frequency log writes inside hot loops can dominate runtime. If CPU remains high after blocking fixes, inspect logging frequency and formatter overhead.

Use rate-limited logs for repetitive idle states.

9. Production hardening checklist

For long-running containerized services:

  • ensure loop waits are blocking where possible
  • enforce reasonable backoff for retries
  • monitor idle and active CPU separately
  • verify cgroup quotas match expectations
  • add health and shutdown behavior tests

This prevents recurrence after future refactors.

Common Pitfalls

  • Using very short sleep intervals and calling it idle waiting.
  • Misreading container CPU percentages without quota context.
  • Polling non-blocking APIs continuously instead of waiting.
  • Tuning code without collecting baseline profiles.
  • Ignoring SIGTERM and forcing hard container kills.

Summary

  • High CPU with loop plus sleep usually indicates polling, quota effects, or both.
  • Replace polling with blocking queues, events, or async waits.
  • Validate Docker cgroup settings before concluding code is broken.
  • Profile idle and active paths to confirm real improvements.
  • Implement graceful shutdown and controlled logging for stable operations.

Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design