惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
Jina AI
Jina AI
月光博客
月光博客
博客园 - Franky
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
Visual Studio Blog
有赞技术团队
有赞技术团队
V
V2EX
IT之家
IT之家
阮一峰的网络日志
阮一峰的网络日志
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
Apple Machine Learning Research
Apple Machine Learning Research
腾讯CDC
D
DataBreaches.Net
Hugging Face - Blog
Hugging Face - Blog
Martin Fowler
Martin Fowler
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
WordPress大学
WordPress大学
C
Check Point Blog
Microsoft Azure Blog
Microsoft Azure Blog
Microsoft Security Blog
Microsoft Security Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Effective On-Call Rotations: Lessons From Building Fair S...
Samson Tanim · 2026-04-23 · via DEV Community
Cover image for Effective On-Call Rotations: Lessons From Building Fair Schedules

Samson Tanimawo

The Rotation Nobody Wants

Our on-call rotation was a spreadsheet. Updated manually. Someone always got scheduled during their vacation. Two people occasionally got double-booked. Holidays were a battleground.

Designing Fair Rotations

Principle 1: Equal Burden Distribution

Track total on-call hours, not just shift count:

def calculate_oncall_burden(engineer, period_days=90):
shifts = get_shifts(engineer, period_days)
return {
'total_hours': sum(s.duration_hours for s in shifts),
'weekend_hours': sum(s.duration_hours for s in shifts if s.is_weekend),
'holiday_hours': sum(s.duration_hours for s in shifts if s.is_holiday),
'night_hours': sum(s.duration_hours for s in shifts if s.is_night),
'pages_received': sum(s.page_count for s in shifts),
'burden_score': calculate_weighted_score(shifts)
}

def calculate_weighted_score(shifts):
"""Weight different types of on-call differently."""
score = 0
for s in shifts:
base = s.duration_hours
if s.is_weekend: base *= 1.5
if s.is_holiday: base *= 2.0
if s.is_night: base *= 1.3
score += base
return round(score, 1)

Enter fullscreen mode Exit fullscreen mode

Principle 2: Respect Preferences

onCall_preferences:
alice:
blackout_dates: ["2024-03-25", "2024-04-01:2024-04-05"] # Vacation
preferred_days: ["Mon", "Tue", "Wed"] # Family on weekends
max_consecutive_days: 3

bob:
blackout_dates: ["2024-04-10"]
preferred_days: ["any"]
max_consecutive_days: 7
prefers_weekends: true # Weekend differential pay

Enter fullscreen mode Exit fullscreen mode

Principle 3: Minimum Pool Size

The math on sustainable rotations:

Pool size Frequency Burnout risk
3 people 1 week on / 2 off HIGH unsustainable
4 people 1 week on / 3 off MEDIUM barely okay
5 people 1 week on / 4 off LOW comfortable
6+ people 1 week on / 5+ off MINIMAL ideal

Rule: Minimum 5 people per rotation.
If you have fewer, reduce on-call scope or hire.

Enter fullscreen mode Exit fullscreen mode

Principle 4: Escalation Tiers

escalation_chain:
tier_1: # Primary on-call
response_time: 5 minutes
scope: all pages

tier_2: # Secondary on-call (backup)
response_time: 15 minutes
scope: escalated or unacknowledged

tier_3: # Engineering manager
response_time: 30 minutes
scope: P1 only or when both T1+T2 unavailable

tier_4: # CTO/VP Engineering
response_time: 60 minutes
scope: Extended P1 (>1 hour), customer escalation

Enter fullscreen mode Exit fullscreen mode

The Override System

Life happens. Make swaps easy:

def request_swap(requesting_engineer, target_date, volunteer=None):
"""Allow easy on-call swaps."""

if volunteer:
# Direct swap: Alice asks Bob to cover
execute_swap(requesting_engineer, volunteer, target_date)
notify_team(f"{requesting_engineer} swapped with {volunteer} for {target_date}")
else:
# Open request: Alice needs coverage, anyone can take it
post_to_channel(
f"{requesting_engineer} needs coverage for {target_date}. "
f"Reply to volunteer. Comp: standard on-call rate."
)

# Key: NO manager approval needed for swaps
# This reduces friction dramatically

Enter fullscreen mode Exit fullscreen mode

Holiday Fairness

The holiday rotation is separate and tracked year-over-year:

holidays_2024 = [
'New Years', 'MLK Day', 'Presidents Day', 'Memorial Day',
'July 4th', 'Labor Day', 'Thanksgiving', 'Christmas'
]

def assign_holidays(team, year):
# Get historical holiday assignments
history = get_holiday_history(team, years=3)

# Sort by who has covered the FEWEST holidays recently
sorted_team = sorted(team, key=lambda e: history.get(e, 0))

assignments = {}
for i, holiday in enumerate(holidays_2024):
engineer = sorted_team[i % len(sorted_team)]
assignments[holiday] = engineer

return assignments

Enter fullscreen mode Exit fullscreen mode

Metrics We Track

Metric Target Current
Burden score variance < 15% 8%
Swap request fulfillment > 95% 98%
Pages per shift (average) < 3 1.8
NPS for on-call experience > 0 +32
Holiday coverage fairness < 1 shift variance 0.5

If you want AI-powered on-call scheduling that optimizes for fairness automatically, check out what we're building at Nova AI Ops.


Written by Dr. Samson Tanimawo
BSc · MSc · MBA · PhD
Founder & CEO, Nova AI Ops. https://novaaiops.com