Build a Notification Service from Scratch — Part 4: Scaling and Deployment
Final part — scale the service for 100K+ notifications/minute and deploy.
Step 1: Horizontal Scaling with Celery
Run multiple Celery workers with concurrency:
# 4 workers, 10 threads each = 40 concurrent deliveries
celery -A app.celery_app worker --concurrency=10 -n worker1@%h
celery -A app.celery_app worker --concurrency=10 -n worker2@%h
celery -A app.celery_app worker --concurrency=10 -n worker3@%h
celery -A app.celery_app worker --concurrency=10 -n worker4@%h
Step 2: Batch Processing
For bulk notifications (e.g., marketing emails), batch processing reduces overhead:
@celery_app.task(bind=True)
def send_batch(self, notification_ids: list[int]):
db = SessionLocal()
notifications = db.query(Notification).filter(Notification.id.in_(notification_ids)).all()
# Group by channel for efficient dispatch
by_channel = {"email": [], "sms": [], "push": []}
for n in notifications:
by_channel[n.channel].append(n)
# Batch email via SendGrid API
if by_channel["email"]:
emails = [{"to": n.recipient, "subject": n.subject, "body": n.body} for n in by_channel["email"]]
sendgrid_client.mail.send(request_body={"personalizations": emails})
db.commit()
db.close()
Step 3: docker-compose for Production
version: '3.8'
services:
api:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://postgres:pass@db:5432/notifications
REDIS_URL: redis://redis:6379
depends_on:
- db
- redis
worker:
build: .
command: celery -A app.celery_app worker --concurrency=10
environment:
DATABASE_URL: postgresql://postgres:pass@db:5432/notifications
REDIS_URL: redis://redis:6379
depends_on:
- db
- redis
deploy:
replicas: 3
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: pass
POSTGRES_DB: notifications
redis:
image: redis:7-alpine
volumes:
pgdata:
Step 4: Performance Tuning
# Connection pooling
engine = create_engine(
settings.database_url,
pool_size=50,
max_overflow=100,
pool_recycle=3600
)
# Redis optimizations
redis_client = redis.Redis(
max_connections=500,
socket_keepalive=True
)
Series Summary
We built a production notification service with:
- Multi-channel support (email, SMS, push)
- Jinja2 templating
- Celery + Redis job queue with retries
- Rate limiting per channel
- Dead letter queue
- Prometheus metrics and alerting
- Horizontal scaling and Docker deployment
Expect 100K+ notifications/minute with 4 workers under $50/month infrastructure cost.
Advertisement