0
# Scheduling & Reliability
1
2
Modal provides comprehensive scheduling and reliability features for automated task execution and resilient function behavior. These capabilities enable building robust serverless applications with automatic retry policies and flexible scheduling patterns.
3
4
## Capabilities
5
6
### Cron Scheduling
7
8
Schedule functions to run at specific times using Unix cron syntax, supporting timezone-aware scheduling for automated tasks.
9
10
```python { .api }
11
class Cron:
12
def __init__(self, cron_string: str, timezone: str = "UTC") -> None:
13
"""Create a cron schedule from cron expression string"""
14
```
15
16
#### Usage Examples
17
18
```python
19
import modal
20
21
app = modal.App("scheduled-tasks")
22
23
# Run every minute
24
@app.function(schedule=modal.Cron("* * * * *"))
25
def check_system_health():
26
"""Monitor system health every minute"""
27
health_status = check_all_services()
28
if not health_status["healthy"]:
29
send_alert(health_status["issues"])
30
return health_status
31
32
# Run daily at 4:05 AM UTC
33
@app.function(schedule=modal.Cron("5 4 * * *"))
34
def daily_backup():
35
"""Perform daily database backup"""
36
print("Starting daily backup...")
37
backup_result = create_database_backup()
38
upload_to_cloud_storage(backup_result)
39
cleanup_old_backups()
40
print("Daily backup completed")
41
42
# Run every Thursday at 9 AM UTC
43
@app.function(schedule=modal.Cron("0 9 * * 4"))
44
def weekly_report():
45
"""Generate weekly analytics report"""
46
report_data = generate_weekly_analytics()
47
send_report_email(report_data)
48
store_report(report_data)
49
50
# Run daily at 6 AM New York time (timezone-aware)
51
@app.function(schedule=modal.Cron("0 6 * * *", timezone="America/New_York"))
52
def business_hours_task():
53
"""Task that runs at business hours in New York"""
54
# Automatically adjusts for daylight saving time
55
process_business_data()
56
57
# Complex cron expressions
58
@app.function(schedule=modal.Cron("15 2 * * 1-5")) # Weekdays at 2:15 AM
59
def weekday_maintenance():
60
"""Maintenance tasks during weekdays"""
61
perform_system_maintenance()
62
63
@app.function(schedule=modal.Cron("0 12 1 * *")) # First day of month at noon
64
def monthly_billing():
65
"""Process monthly billing on first day of month"""
66
process_monthly_invoices()
67
send_billing_notifications()
68
```
69
70
### Period Scheduling
71
72
Schedule functions to run at regular intervals using natural time periods, supporting precise timing for recurring tasks.
73
74
```python { .api }
75
class Period:
76
def __init__(
77
self,
78
*,
79
years: int = 0,
80
months: int = 0,
81
weeks: int = 0,
82
days: int = 0,
83
hours: int = 0,
84
minutes: int = 0,
85
seconds: float = 0
86
) -> None:
87
"""Create a periodic schedule with specified intervals"""
88
```
89
90
#### Usage Examples
91
92
```python
93
import modal
94
95
app = modal.App("periodic-tasks")
96
97
# Run every day
98
@app.function(schedule=modal.Period(days=1))
99
def daily_data_sync():
100
"""Synchronize data daily"""
101
sync_external_apis()
102
update_local_cache()
103
print("Daily sync completed")
104
105
# Run every 4 hours
106
@app.function(schedule=modal.Period(hours=4))
107
def frequent_health_check():
108
"""Check system health every 4 hours"""
109
status = perform_detailed_health_check()
110
log_health_metrics(status)
111
112
# Run every 15 minutes
113
@app.function(schedule=modal.Period(minutes=15))
114
def cache_refresh():
115
"""Refresh application cache every 15 minutes"""
116
refresh_application_cache()
117
update_cache_metrics()
118
119
# Run every 30 seconds
120
@app.function(schedule=modal.Period(seconds=30))
121
def high_frequency_monitor():
122
"""High-frequency monitoring task"""
123
collect_realtime_metrics()
124
check_alert_conditions()
125
126
# Complex periods
127
@app.function(schedule=modal.Period(weeks=2, days=3)) # Every 17 days
128
def bi_weekly_plus_task():
129
"""Task that runs every 2 weeks + 3 days"""
130
perform_special_analysis()
131
132
@app.function(schedule=modal.Period(months=1)) # Monthly (same day each month)
133
def monthly_report():
134
"""Generate monthly report on same day each month"""
135
# Handles varying month lengths correctly
136
generate_monthly_analytics()
137
archive_old_data()
138
139
# High precision timing
140
@app.function(schedule=modal.Period(seconds=3.14159)) # Every π seconds
141
def precise_timing_task():
142
"""Task with precise timing requirements"""
143
collect_high_precision_measurements()
144
```
145
146
### Retry Policies
147
148
Configure automatic retry behavior for functions to handle transient failures and improve reliability.
149
150
```python { .api }
151
class Retries:
152
def __init__(
153
self,
154
*,
155
max_retries: int,
156
backoff_coefficient: float = 2.0,
157
initial_delay: float = 1.0,
158
max_delay: float = 60.0
159
) -> None:
160
"""Configure retry policy with exponential or fixed backoff"""
161
```
162
163
#### Usage Examples
164
165
```python
166
import modal
167
168
app = modal.App("reliable-functions")
169
170
# Simple retry configuration
171
@app.function(retries=4) # Retry up to 4 times with default settings
172
def simple_retry_task():
173
"""Task with basic retry policy"""
174
result = call_external_api()
175
return result
176
177
# Fixed-interval retries
178
@app.function(
179
retries=modal.Retries(
180
max_retries=3,
181
backoff_coefficient=1.0, # Fixed delay
182
initial_delay=5.0, # 5 seconds between retries
183
)
184
)
185
def fixed_retry_task():
186
"""Task with fixed 5-second delay between retries"""
187
try:
188
return process_critical_data()
189
except Exception as e:
190
print(f"Attempt failed: {e}")
191
raise # Will trigger retry
192
193
# Exponential backoff
194
@app.function(
195
retries=modal.Retries(
196
max_retries=5,
197
backoff_coefficient=2.0, # Double delay each retry
198
initial_delay=1.0, # Start with 1 second
199
max_delay=30.0 # Cap at 30 seconds
200
)
201
)
202
def exponential_backoff_task():
203
"""Task with exponential backoff: 1s, 2s, 4s, 8s, 16s delays"""
204
return call_rate_limited_service()
205
206
# Custom backoff configuration
207
@app.function(
208
retries=modal.Retries(
209
max_retries=6,
210
backoff_coefficient=1.5, # 50% increase each retry
211
initial_delay=2.0, # Start with 2 seconds
212
max_delay=60.0 # Cap at 1 minute
213
)
214
)
215
def custom_backoff_task():
216
"""Custom retry pattern: 2s, 3s, 4.5s, 6.75s, 10.125s, 15.1875s"""
217
return perform_database_operation()
218
219
# Retry with error handling
220
@app.function(
221
retries=modal.Retries(
222
max_retries=3,
223
initial_delay=1.0,
224
backoff_coefficient=2.0
225
)
226
)
227
def resilient_task():
228
"""Task with retry and comprehensive error handling"""
229
attempt = 0
230
try:
231
# Simulate getting current attempt (in real usage, this would be tracked differently)
232
result = unreliable_external_service()
233
print(f"Success on attempt {attempt + 1}")
234
return result
235
except TemporaryError as e:
236
print(f"Temporary error on attempt {attempt + 1}: {e}")
237
raise # Retry
238
except PermanentError as e:
239
print(f"Permanent error, not retrying: {e}")
240
return {"error": "permanent_failure", "message": str(e)}
241
```
242
243
## Advanced Scheduling Patterns
244
245
### Multi-Timezone Scheduling
246
247
```python
248
import modal
249
250
app = modal.App("global-scheduling")
251
252
# Different tasks in different timezones
253
@app.function(schedule=modal.Cron("0 9 * * *", timezone="America/New_York"))
254
def us_business_hours():
255
"""Run at 9 AM Eastern Time"""
256
process_us_customer_data()
257
258
@app.function(schedule=modal.Cron("0 9 * * *", timezone="Europe/London"))
259
def uk_business_hours():
260
"""Run at 9 AM London Time"""
261
process_uk_customer_data()
262
263
@app.function(schedule=modal.Cron("0 9 * * *", timezone="Asia/Tokyo"))
264
def japan_business_hours():
265
"""Run at 9 AM Japan Time"""
266
process_japan_customer_data()
267
268
@app.function(schedule=modal.Cron("0 0 * * *", timezone="UTC"))
269
def global_midnight_task():
270
"""Run at midnight UTC (global coordination)"""
271
consolidate_global_reports()
272
```
273
274
### Cascading Scheduled Tasks
275
276
```python
277
import modal
278
279
app = modal.App("cascading-tasks")
280
281
# Shared queue for task coordination
282
task_queue = modal.Queue.persist("cascade-queue")
283
284
@app.function(schedule=modal.Cron("0 2 * * *")) # 2 AM daily
285
def stage_1_data_extraction():
286
"""First stage: Extract data"""
287
print("Stage 1: Starting data extraction...")
288
raw_data = extract_raw_data()
289
290
# Queue next stage
291
task_queue.put({
292
"stage": "transform",
293
"data_id": raw_data["id"],
294
"timestamp": time.time()
295
})
296
297
print("Stage 1: Data extraction completed")
298
299
@app.function(schedule=modal.Period(minutes=5)) # Check every 5 minutes
300
def stage_2_data_transformation():
301
"""Second stage: Transform data when available"""
302
try:
303
task = task_queue.get(timeout=1) # Non-blocking check
304
if task and task["stage"] == "transform":
305
print(f"Stage 2: Transforming data {task['data_id']}")
306
transformed_data = transform_data(task["data_id"])
307
308
# Queue final stage
309
task_queue.put({
310
"stage": "load",
311
"data_id": transformed_data["id"],
312
"timestamp": time.time()
313
})
314
315
print("Stage 2: Data transformation completed")
316
except:
317
# No tasks available, continue
318
pass
319
320
@app.function(schedule=modal.Period(minutes=10)) # Check every 10 minutes
321
def stage_3_data_loading():
322
"""Final stage: Load data when ready"""
323
try:
324
task = task_queue.get(timeout=1)
325
if task and task["stage"] == "load":
326
print(f"Stage 3: Loading data {task['data_id']}")
327
load_data_to_warehouse(task["data_id"])
328
send_completion_notification(task["data_id"])
329
print("Stage 3: Data loading completed")
330
except:
331
pass
332
```
333
334
### Conditional Scheduling with Circuit Breaker
335
336
```python
337
import modal
338
339
app = modal.App("reliable-scheduling")
340
341
# Shared state for circuit breaker
342
circuit_state = modal.Dict.persist("circuit-breaker")
343
344
@app.function(
345
schedule=modal.Period(minutes=10),
346
retries=modal.Retries(max_retries=2, initial_delay=30.0)
347
)
348
def monitored_task():
349
"""Task with circuit breaker pattern"""
350
# Check circuit breaker state
351
is_open = circuit_state.get("circuit_open", False)
352
failure_count = circuit_state.get("failure_count", 0)
353
last_failure = circuit_state.get("last_failure_time", 0)
354
355
# Circuit breaker logic
356
if is_open:
357
# Check if enough time has passed to try again
358
if time.time() - last_failure > 300: # 5 minutes
359
print("Circuit breaker: Attempting to close circuit")
360
circuit_state["circuit_open"] = False
361
else:
362
print("Circuit breaker: Circuit is open, skipping execution")
363
return {"status": "circuit_open"}
364
365
try:
366
# Attempt the operation
367
result = potentially_failing_operation()
368
369
# Success - reset failure count
370
circuit_state["failure_count"] = 0
371
circuit_state["circuit_open"] = False
372
373
print("Task completed successfully")
374
return {"status": "success", "result": result}
375
376
except Exception as e:
377
# Handle failure
378
new_failure_count = failure_count + 1
379
circuit_state["failure_count"] = new_failure_count
380
circuit_state["last_failure_time"] = time.time()
381
382
# Open circuit if too many failures
383
if new_failure_count >= 3:
384
circuit_state["circuit_open"] = True
385
print("Circuit breaker: Opening circuit due to repeated failures")
386
387
print(f"Task failed (attempt {new_failure_count}): {e}")
388
raise # Will trigger retry policy
389
```
390
391
### Dynamic Scheduling Based on Load
392
393
```python
394
import modal
395
396
app = modal.App("adaptive-scheduling")
397
398
# Shared metrics storage
399
metrics = modal.Dict.persist("system-metrics")
400
401
@app.function(schedule=modal.Period(seconds=30))
402
def collect_system_metrics():
403
"""Collect system metrics every 30 seconds"""
404
current_load = get_system_load()
405
queue_depth = get_queue_depth()
406
error_rate = get_error_rate()
407
408
metrics.update({
409
"load": current_load,
410
"queue_depth": queue_depth,
411
"error_rate": error_rate,
412
"timestamp": time.time()
413
})
414
415
@app.function(schedule=modal.Period(minutes=1))
416
def adaptive_processor():
417
"""Process tasks with adaptive frequency based on load"""
418
current_metrics = metrics.get("load", 0)
419
queue_depth = metrics.get("queue_depth", 0)
420
421
# Adapt processing based on system state
422
if current_metrics < 0.5 and queue_depth > 100:
423
# Low load, high queue - process more aggressively
424
batch_size = 50
425
process_batch_tasks(batch_size)
426
print(f"High throughput mode: processed {batch_size} tasks")
427
428
elif current_metrics > 0.8:
429
# High load - reduce processing
430
batch_size = 10
431
process_batch_tasks(batch_size)
432
print(f"Conservative mode: processed {batch_size} tasks")
433
434
else:
435
# Normal processing
436
batch_size = 25
437
process_batch_tasks(batch_size)
438
print(f"Normal mode: processed {batch_size} tasks")
439
440
@app.function(
441
schedule=modal.Period(minutes=5),
442
retries=modal.Retries(max_retries=3, initial_delay=60.0)
443
)
444
def health_based_processing():
445
"""Processing task that adapts based on system health"""
446
error_rate = metrics.get("error_rate", 0)
447
448
if error_rate > 0.1: # More than 10% error rate
449
print("High error rate detected, running diagnostic tasks")
450
run_system_diagnostics()
451
return {"status": "diagnostics"}
452
else:
453
print("System healthy, running normal processing")
454
return process_normal_workload()
455
```