// Create task (with optional schedule)
POST /tasks -> Task
{
id, //output
name,
description,
schedule, // optional, crontab format
definition, // oneof url to python code in blob storage, or container image
parameters, // key-> value
createTime,
owner, // FK -> User table
status, // output
}
// List, get, pause, resume, delete
GET /tasks -> Task[]
GET /tasks/
PATCH /tasks/
DELETE /tasks/
// Get execution status
GET /tasks/
GET /tasks/
{
id, //output
taskId, // FK -> Task table
scheduledTime,
startTime,
lastUpdateTime,
endTime,
status,
errorCode,
errorDetails,
logUrl,
}
Task table:
Execution table:
Based on database size estimate, we choose a relational database, sharded by taskId. Say we create 4 shards, so each DB is ~50Tb in 3 years.
A KV store like DynamoDB would also work well here.
API Gateway: responsible for load balancing, SSL termination, rate limiting, etc.
Task Service: responsible for implementing management endpoint for tasks and executions
Database: stores the metadata for tasks and executions
Task Queue: a queue that reliably and durable stores task executions.
Execution Service: responsible for pulling work off task queue and execute the workload
Container Runtime: runtime (e.g. k8s) for containers
Log: blob storage for logs
Tasks with immediate execution:
Scheduled tasks:
Monitoring:
Task service and task queue
Workloads:
Scheduling
Monitoring execution:
Scheduling
At least once execution:
Task service and execution service are stateless and can be horizontally scaled for high availability.
The database can be sharded by task id. As mentioned above, having 4 shards can support max metadata storage needs in 3 years. We also estimate read/write qps: assuming most of the read/write comes from task monitoring, then we have 100k / 1min = 2k qps. This is within the capabilities of a database especically when it is already sharded.
Task queue: estimation is 100k / 1min = 2k messages per second. if we use a managed queue like SQS or Spanner queue, we don't need to worry about sharding. Even for Kafka this is well within the limits of one shard.
Data could grow and exceed our system limits. We implement retention policy for all storage layers (database, queue, blob) for 1 year.
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?