Skip to main content

Command Palette

Search for a command to run...

Can Python Really Scale?

A Deep Dive into High-Volume Event-Driven Systems with Kafka, Python and Kubernetes

Updated
7 min readView as Markdown
Can Python Really Scale?
D
I'm Ayyanar Jeyakrishnan ; aka AJ. With over 21 years in IT, I'm a passionate Multi-Cloud Architect specialising in crafting scalable and efficient cloud solutions. I've successfully designed and implemented multi-cloud architectures for diverse organisations, harnessing AWS, Azure, and GCP. My track record includes delivering Machine Learning and Data Platform projects with a focus on high availability, security, and scalability. I'm a proponent of DevOps and MLOps methodologies, accelerating development and deployment. I actively engage with the tech community, sharing knowledge in sessions, conferences, and mentoring programs. Constantly learning and pursuing certifications, I provide cutting-edge solutions to drive success in the evolving cloud and AI/ML landscape.

Executive Summary

The statement “Python doesn't scale” is too simplistic.

Python can scale very well horizontally. Kafka provides durable event streaming and buffering, and Kubernetes provides horizontal scaling.

However, high-volume systems expose trade-offs that are easy to miss at smaller scale:

  • CPU efficiency and the GIL for CPU-bound workloads

  • Memory footprint

  • Kafka partition limits

  • Consumer-group rebalancing

  • Autoscaling based on the wrong metric

  • Database connection multiplication

  • Kubernetes CPU throttling

  • Retry storms

  • Duplicate processing during pod termination

  • Container startup and recovery time

  • Tail latency and operational cost

This article explores these issues through 10 production-oriented scenarios.

For reference, consider a system processing:

90,000 transactions/minute ≈ 1,500 events/second.

The exact sustainable throughput will always depend on payload size, processing complexity, downstream systems, latency requirements, and infrastructure configuration.


1. Reference Architecture

The architecture looks simple.

Production scalability is not determined by the number of boxes in the diagram. It is determined by what happens inside every arrow.


2. Scenario 1 — CPU Bound Python and the GIL

The first limitation appears when a Kafka consumer isn't simply making database or API calls.

Suppose every event requires:

  • Complex transformation

  • Validation

  • Calculation

  • Data enrichment

  • Parsing

  • CPU-intensive business logic

Kafka
  ↓
Python Consumer
  ↓
Deserialize
  ↓
Business Logic
  ↓
CPU-heavy calculation
  ↓
Database

At some point, CPU utilization reaches 100%.

The natural reaction is:

“Let's add more threads.”

For CPU-bound Python code, the GIL can limit how effectively multiple threads execute Python bytecode simultaneously within a single process.

The usual response is to scale using multiple processes and/or Kubernetes pods.

Architectural implication

Scale Python horizontally with processes/pods, or isolate CPU-intensive processing into a specialized service.


3. Scenario 2 — 100 Kubernetes Pods Don't Mean 100 Kafka Consumers

Now let's solve the CPU problem by scaling out.

We deploy:

100 Python consumer pods.

But our Kafka topic contains only:

20 partitions.

Within a Kafka consumer group, a partition is consumed by one consumer at a time.

This leads to an important architecture principle:

Kubernetes scalability is constrained by Kafka partitioning.

Partition planning should consider throughput, peak load, processing time, ordering requirements, key distribution, and future growth.


4. Scenario 3 — CPU Looks Healthy but Kafka Lag Is Exploding

Imagine:

CPU utilization:       40%
Memory utilization:    55%
Pods:                  20
Kafka consumer lag:    10,000,000

Your consumers aren't CPU-bound. They're waiting.

Perhaps the database is slow. Perhaps an external API is slow. Perhaps connection pools are exhausted.

CPU-based HPA can therefore conclude that no additional pods are needed while Kafka continues accumulating work.

The principle:

Scale on the signal that represents work waiting to be processed.


5. Scenario 4 — The Database Connection Explosion

Suppose each Python pod maintains 20 database connections.

Initially:

10 pods × 20 connections = 200 connections

After autoscaling:

100 pods × 20 connections = 2,000 connections

The application has scaled. The database hasn't.

Kafka
 ↓
100 Python pods
 ↓
2,000 DB connections
 ↓
Database
 ↓
Overload

This is a classic scaling multiplier problem.

Controls

Consider:

  • Bounded connection pools

  • Connection proxies/poolers

  • Database capacity planning

  • Caching

  • Batching

  • Asynchronous writes

  • Backpressure

  • Limits on maximum consumer concurrency

Scaling the consumer fleet does not mean the entire system has scaled.


6. Scenario 5 — Kubernetes CPU Throttling

Suppose the container has:

CPU limit = 1 core

but the workload temporarily needs:

1.8 cores

The container can be throttled.

CPU demand ↑
     ↓
CPU throttling
     ↓
Processing time ↑
     ↓
Kafka lag ↑
     ↓
P99 latency ↑

CPU requests and limits should be based on real load testing, latency targets, burst requirements, and cluster capacity rather than arbitrary defaults.


7. Scenario 6 — Kafka Consumer Rebalancing During Kubernetes Scaling

Initially:

6 consumer pods

A traffic spike causes:

6 → 12 pods

Kafka needs to redistribute partitions among the consumer group.

Normal processing
      ↓
Scale-out
      ↓
Consumer group membership changes
      ↓
Rebalance
      ↓
Partition reassignment
      ↓
Processing resumes

Frequent scaling can therefore create unnecessary churn.

Avoid aggressive scale-up/down policies, use sensible cooldown periods, and avoid noisy scaling signals.


8. Scenario 7 — Retry Storm and Cascading Failure

Suppose an external service becomes slow.

Request
  ↓
Timeout
  ↓
Retry
  ↓
Timeout
  ↓
Retry

Now Kubernetes sees increased workload and scales the consumer fleet.

Instead of solving the problem, more clients hit the already overloaded service.

Downstream failure
       ↓
Retries increase
       ↓
Traffic increases
       ↓
Downstream overload increases
       ↓
Failures increase
       ↓
Retries increase

This is a positive feedback loop.

Required controls

  • Exponential backoff

  • Jitter

  • Bounded retries

  • Circuit breakers

  • Rate limiting

  • Bulkheads

  • Dead-letter queues

  • Sensible timeouts

Never allow a failed dependency to cause unbounded additional work.


9. Scenario 8 — Pod Termination and Duplicate Kafka Processing

Kubernetes terminates pods during deployments, scaling, node maintenance, upgrades, spot interruption, and resource pressure.

Consider:

Event 123
   ↓
Consumer A
   ↓
Processing interrupted
   ↓
Offset not committed
   ↓
Consumer B
   ↓
Event 123 processed again

This is why Kafka consumers should generally be designed around at-least-once processing semantics, unless stronger end-to-end guarantees have deliberately been engineered.

Graceful shutdown

SIGTERM
   ↓
Stop accepting new work
   ↓
Finish in-flight processing
   ↓
Commit offsets safely
   ↓
Close DB connections
   ↓
Close Kafka consumer
   ↓
Exit

Business operations should also be idempotent.


10. Scenario 9 — Python Memory Pressure and Kubernetes OOMKill

Python applications can hold significant amounts of memory depending on object allocation, batch size, payload size, JSON deserialization, libraries, caches, and database results.

Consider:

Memory limit = 512 MB

Memory increases:

300 MB
  ↓
400 MB
  ↓
480 MB
  ↓
512 MB
  ↓
OOMKilled

Then:

Pod restart
 ↓
Kafka consumer rebalance
 ↓
Processing interruption
 ↓
Potential duplicate work
 ↓
Kafka lag

Mitigation

  • Bounded batch sizes

  • Streaming instead of loading huge payloads

  • Memory profiling

  • Appropriate memory requests/limits

  • Avoid unnecessary object duplication

  • Controlled caches

  • Backpressure


11. Scenario 10 — The Full Cascade

The previous scenarios are not independent.

A real production failure can look like:

Traffic spike
     ↓
Kafka ingress increases
     ↓
Consumer lag increases
     ↓
KEDA scales consumers
     ↓
Kafka consumer rebalance
     ↓
More DB connections
     ↓
Database becomes saturated
     ↓
DB latency increases
     ↓
Python processing slows
     ↓
Kafka lag increases
     ↓
Consumers retry downstream requests
     ↓
Retry traffic increases
     ↓
Kubernetes scales more pods
     ↓
Database gets even more connections
     ↓
System becomes unstable

The individual components may all be working correctly.

The system as a whole can still fail.

2 views