Skip to content

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

ScenarioCloud TasksPub/SubWhy
Single handlerTasks designed for 1:1 mapping
Multiple handlersPub/Sub designed for 1:N
Scheduled tasksTasks supports scheduling
Explicit rate controlTasks has strict QPS limits
Multiple subscribersNeed Pub/Sub for broadcast
Decoupled architecturePub/Sub better for loose coupling
Ordered deliveryBoth support, different model
Message size > 1MBPub/Sub supports up to 10MB
Dead letter queuesPub/Sub has native DLQ
At-least-onceBoth guarantee (need idempotency)
Low latency criticalBoth 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 engaged

Pub/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 broadcast

Pub/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 backoff

Use 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 scheduling

Use 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 separately

When 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 change

Use 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 subscribers

Use 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 management

Hybrid: 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 handlers

Pattern 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 fulfillment

Quotas & 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,000

Pub/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 messages

Decision 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)

References