Cloud Tasks vs Pub/Sub — Decision Matrix
Tại Sao Quan Trọng
Nhiều người nhầm lẫn khi chọn giữa hai service. Chọn sai:
- Tasks cho Pub/Sub use case → bạn được cài explicit invocation (không cần)
- Pub/Sub cho Tasks use case → bạn mất rate control, single publisher (cần)
Key difference: Explicit vs implicit invocation.
Invocation Model Comparison
Cloud Tasks: Explicit Invocation
Publisher → "Execute task at https://api.example.com"
↓
Cloud Tasks (manages dispatch)
↓
Handler (at specified URL)Characteristics:
- Publisher specifies exact endpoint
- Publisher controls rate, retry, schedule
- Single handler per task (no broadcast)
- Use case: "I need this specific thing done"
Pub/Sub: Implicit Invocation
Publisher → "An order was placed" (event)
↓
Pub/Sub (routes to subscribers)
↓
Handler A (maybe email service)
Handler B (maybe payment service)
Handler C (maybe inventory service)Characteristics:
- Publisher doesn't know handlers
- Pub/Sub decides routing (multiple subscribers)
- Loosely coupled (easy to add/remove handlers)
- Use case: "Something happened, tell everyone interested"
Decision Matrix
| Scenario | Cloud Tasks | Pub/Sub | Why |
|---|---|---|---|
| Single handler | ✓ | ○ | Tasks designed for 1:1 mapping |
| Multiple handlers | ✗ | ✓ | Pub/Sub designed for 1:N |
| Scheduled tasks | ✓ | ✗ | Tasks supports scheduling |
| Explicit rate control | ✓ | ○ | Tasks has strict QPS limits |
| Multiple subscribers | ✗ | ✓ | Need Pub/Sub for broadcast |
| Decoupled architecture | ○ | ✓ | Pub/Sub better for loose coupling |
| Ordered delivery | ✓ | ✓ | Both support, different model |
| Message size > 1MB | ✗ | ✓ | Pub/Sub supports up to 10MB |
| Dead letter queues | ○ | ✓ | Pub/Sub has native DLQ |
| At-least-once | ✓ | ✓ | Both guarantee (need idempotency) |
| Low latency critical | ○ | ○ | Both good, depends on handler |
Detailed Comparison
Rate Limiting & Control
Cloud Tasks:
max_dispatches_per_second: 500 per queue (configurable)
max_burst_size: 100 (explicit)
max_concurrent_dispatches: 1,000 (explicit)
Publisher controls dispatch rate precisely
Use case: "Don't overwhelm my backend (capacity: 100 QPS)"Pub/Sub:
No explicit rate limits (unlimited)
Subscriber controls consumption (pull-based)
Publisher not limited, but subscribers can throttle
Use case: "Publish all events, subscribers decide pace"Scenario: Payment Processing
Cloud Tasks:
CreateTask(url="https://payment-processor.internal/charge", {
rate_limit: 100 QPS, // Don't charge faster than processor handles
schedule_time: now, // Charge immediately
})
Publisher explicitly: rate limit known, scheduler engagedPub/Sub:
Publish(topic="payment.charge_requested", {
order_id: "12345",
amount: 100
})
Subscription pulls at own pace (handled separately)Scenario: Newsletter Broadcast
Cloud Tasks:
// For each subscriber
CreateTask(url="https://email-service.internal/send?user=john@example.com", {
user_email: "john@example.com"
})
CreateTask(url="https://email-service.internal/send?user=jane@example.com", {
user_email: "jane@example.com"
})
...1,000,000 tasks
Problem: Million tasks to create, rate-limited queue
Inefficient for broadcastPub/Sub:
Publish(topic="newsletter.send", {
content: "Q1 Newsletter"
})
Subscription A: Email service (pulls, sends 1M emails)
Subscription B: Analytics (counts, tracks opens)
Subscription C: Archive (stores for compliance)
All from single event. Efficient.When To Choose Cloud Tasks
Use Case 1: Explicit Control Required
Scenario: Payment processing
Requirements:
- Charge exactly one time (idempotent)
- Don't charge faster than processor handles (rate-limited)
- Charge immediately or schedule for later
- Retry with specific backoff
Solution: Cloud Tasks
- Task ID ensures idempotency
- Rate limits prevent overload
- schedule_time for later
- Configurable exponential backoffUse Case 2: Deferred Work
Scenario: User uploads video, want to transcode asynchronously
Requirements:
- Transcode specific video (one handler per task)
- Transcode immediately or schedule later (3 AM batch)
- Retry if fails
Solution: Cloud Tasks
- Task = specific video file
- Can schedule for 3 AM
- Retry automatically
NOT Pub/Sub:
- Pub/Sub broadcasts "video uploaded"
- Multiple subscribers complicates schedulingUse Case 3: Cross-Service Coordination
Scenario: Order fulfillment
1. Create order (write to DB)
2. Charge payment
3. Notify email service
4. Update inventory
Requirements:
- Explicit ordering (payment before email)
- Rate limit each step
- Retry each step independently
Solution: Order → Task 1 (charge) → Task 2 (email) → Task 3 (inventory)
Each task explicit, rate-limited separatelyWhen To Choose Pub/Sub
Use Case 1: Multiple Handlers
Scenario: User purchases product
Events:
1. Email confirmation to customer
2. SMS to warehouse (start packing)
3. Analytics (track conversion)
4. Recommendations (update model)
5. Accounting (record sale)
Requirements:
- One event, multiple handlers
- Handlers can be added/removed without changing publisher
- Handlers scale independently
Solution: Pub/Sub
- Publish "purchase.completed" event
- 5 subscriptions (different handlers)
- Easy to add 6th handler later (discovery problem)
NOT Cloud Tasks:
- Would need to create 5 separate tasks per purchase
- Publisher must know all 5 endpoints (coupling)
- Adding new handler requires code changeUse Case 2: Event-Driven Architecture
Scenario: SaaS platform with multiple services
- Customer service
- Billing service
- Support service
- Analytics service
- Audit service
Events:
- User.created
- Subscription.upgraded
- Payment.processed
- Support.ticket.created
Requirements:
- Event published once
- Many services interested (different sets)
- Services can join/leave without coordinating
Solution: Pub/Sub with topics
- Customer service subscribes to all user events
- Billing subscribes to subscription/payment events
- Analytics subscribes to all events
- New service can subscribe to relevant topics
NOT Cloud Tasks:
- Would need Task queue for each service combo
- Coupling: who publishes must know subscribersUse Case 3: Stream Processing
Scenario: Real-time analytics
- 1M events/second
- Want to process in parallel
- Process batches with Dataflow
Requirements:
- High throughput
- Parallel processing
- Retention for reprocessing
Solution: Pub/Sub
- Pub/Sub handles 1M events/sec
- Dataflow pulls, processes in parallel
- Replay capability via seek
NOT Cloud Tasks:
- 500 QPS per queue → would need 2,000 queues
- Excessive managementHybrid: Using Both
Scenario: Complex fulfillment system
Step 1: Order placed → Pub/Sub event
"order.created" → multiple subscribers wake up
Step 2: Payment service (one of subscribers) starts workflow
- Create Task 1: Charge payment
- Create Task 2 (depends on Task 1): Email receipt
- Create Task 3 (depends on Task 2): Update inventory
Step 3: Fraud service (another subscriber) analyzes
- Create Task: Run fraud check
- Publish "order.fraud_cleared" event (if OK)
Pattern:
- Pub/Sub for coarse-grained events (order.created)
- Cloud Tasks for fine-grained workflow (payment → receipt → inventory)Implementation Patterns
Pattern 1: Tasks → Pub/Sub Fanout
go
// Handler for Cloud Tasks
func ProcessOrderHandler(w http.ResponseWriter, r *http.Request) {
var order Order
json.NewDecoder(r.Body).Decode(&order)
// Task: Process single order
err := chargePayment(order)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
// Publish event for other services to react
pubsub.Publish(ctx, "order.payment_processed", Order{
OrderID: order.OrderID,
Amount: order.Amount,
})
w.WriteHeader(http.StatusOK)
}
// Result: Cloud Tasks handles explicit ordering + rate control
// Pub/Sub handles broadcast to multiple handlersPattern 2: Pub/Sub → Tasks Dispatch
go
// Pub/Sub subscription handler
func PublishOrderEventHandler(ctx context.Context, msg *pubsub.Message) error {
var order Order
json.Unmarshal(msg.Data, &order)
// Dispatch specific tasks for this order
for i := 0; i < order.LineItems; i++ {
client.CreateTask(ctx, &TaskRequest{
Parent: "projects/X/locations/Y/queues/fulfillment",
Task: &Task{
Name: fmt.Sprintf("...tasks/order-%s-item-%d", order.OrderID, i),
HttpRequest: &HttpRequest{
Url: "https://warehouse.internal/fulfill",
Body: []byte(fmt.Sprintf(`{"order_id": "%s", "item_index": %d}`, order.OrderID, i)),
},
},
})
}
msg.Ack()
return nil
}
// Result: Pub/Sub broadcasts event, Tasks handle per-item fulfillmentQuotas & Limits
Cloud Tasks
Queues per region: 1,000
Tasks per queue: unlimited
Dispatch rate per queue: 500 QPS (adjustable)
Max task size: 1 MB
Task retention: 31 days
Max concurrent per queue: 1,000Pub/Sub
Topics per project: 10,000
Subscriptions per project: 10,000
Publish rate: unlimited (millions/sec possible)
Max message size: 10 MB
Message retention: 7 days (default, up to 31 days)
Pull batch size: 1,000 messagesDecision Flowchart
┌─ Do you need multiple handlers for one event?
│ ├─ YES → Pub/Sub
│ └─ NO → Continue...
│
├─ Do you need explicit rate limiting per task?
│ ├─ YES → Cloud Tasks
│ └─ NO → Continue...
│
├─ Do you need to schedule tasks for later?
│ ├─ YES → Cloud Tasks
│ └─ NO → Continue...
│
├─ Do you need ordered delivery per key?
│ ├─ YES → Either (Pub/Sub via ordering key, Tasks via FIFO)
│ └─ NO → Continue...
│
└─ Do you need loosely-coupled architecture?
├─ YES → Pub/Sub (easier to add handlers)
└─ NO → Cloud Tasks (simpler for single handler)