DevOpsIndex

GCP Messaging — Pub/Sub, Cloud Tasks, Eventarc


Messaging Service Map

Use case AWS GCP
Fan-out / pub-sub SNS + SQS Cloud Pub/Sub
Reliable task queue SQS Cloud Tasks
Event stream (Kafka-like) Kinesis Cloud Pub/Sub
Managed Kafka MSK Managed Kafka for BigQuery / Pub/Sub
Event routing EventBridge Eventarc
Scheduled triggers EventBridge Scheduler Cloud Scheduler

Cloud Pub/Sub

Pub/Sub is GCP's core messaging backbone. It's both SNS (fan-out) and SQS (queue) in one service. Globally distributed, scales to millions of messages per second automatically.

graph LR
    classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff
    classDef orange fill:#e67e22,stroke:#d35400,color:#fff
    classDef green fill:#2ecc71,stroke:#27ae60,color:#fff
    classDef blue fill:#3498db,stroke:#2980b9,color:#fff

    PUB1["Publisher: Order Service"]:::orange
    PUB2["Publisher: Inventory Service"]:::orange
    TOPIC["Topic: order-events"]:::gcp
    SUB1["Subscription: email-sub<br/>(pull)"]:::blue
    SUB2["Subscription: analytics-sub<br/>(pull)"]:::blue
    SUB3["Subscription: notification-sub<br/>(push → Cloud Run)"]:::green

    PUB1 --> TOPIC
    PUB2 --> TOPIC
    TOPIC --> SUB1
    TOPIC --> SUB2
    TOPIC --> SUB3

Each subscription gets an independent copy of every message. Pull subscriptions buffer messages. Push subscriptions deliver to an HTTP endpoint.

Setup

# Create topic
gcloud pubsub topics create order-events

# Pull subscription (consumer polls for messages)
gcloud pubsub subscriptions create email-sub \
  --topic=order-events \
  --ack-deadline=60s \           # how long to process before redelivery (like SQS visibility timeout)
  --message-retention-duration=7d  # how long to retain unacked messages

# Push subscription (Pub/Sub delivers to HTTP endpoint)
gcloud pubsub subscriptions create notification-sub \
  --topic=order-events \
  --push-endpoint=https://my-cloud-run.run.app/events \
  --push-auth-service-account=pubsub-invoker@project.iam.gserviceaccount.com

Publish Messages

# Publish a message (CLI)
gcloud pubsub topics publish order-events \
  --message='{"order_id":"123","status":"placed"}' \
  --attribute="source=order-service,version=1"
from google.cloud import pubsub_v1
import json

publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path("my-project", "order-events")

message = {
    "order_id": "123",
    "status": "placed",
    "amount": 99.99
}

future = publisher.publish(
    topic_path,
    data=json.dumps(message).encode("utf-8"),
    source="order-service",        # message attributes (like SQS message attributes)
    version="1"
)
print(f"Published: {future.result()}")

Pull (Consumer)

from google.cloud import pubsub_v1
import json

subscriber = pubsub_v1.SubscriberClient()
subscription_path = subscriber.subscription_path("my-project", "email-sub")

def callback(message: pubsub_v1.subscriber.message.Message):
    try:
        data = json.loads(message.data.decode("utf-8"))
        send_email(data["order_id"])
        message.ack()                    # acknowledge — remove from queue
    except Exception as e:
        print(f"Error: {e}")
        message.nack()                   # nack — redeliver (like SQS visibility timeout reset)

streaming_pull = subscriber.subscribe(subscription_path, callback=callback)
streaming_pull.result()                  # block and process

Dead Letter Topics

# Create dead letter topic (for failed messages after N attempts)
gcloud pubsub topics create order-events-dlq

gcloud pubsub subscriptions modify-push-config email-sub \
  --dead-letter-topic=order-events-dlq \
  --max-delivery-attempts=5         # after 5 nacks → goes to DLQ

Pub/Sub Lite vs Pub/Sub

Pub/Sub Pub/Sub Lite
Infrastructure Fully managed, Google-handled Zonal or regional partitions
Scaling Automatic Manual (configure capacity)
Ordering No (unless ordering key) Yes (partition-based)
Cost Higher 90% cheaper for high throughput
Use case Default, variable load Predictable high-volume (Kafka-like)

Pub/Sub vs AWS Services

GCP Pub/Sub AWS SQS AWS SNS AWS Kinesis
Model Topic + subscriptions Queue (point-to-point) Topic (fan-out) Sharded stream
Fan-out Yes (multiple subscriptions) No Yes No
Retention 7 days (up to 600 days lite) 14 days No retention 7 days (up to 365)
Ordering Optional (ordering key) Optional (FIFO) No Per shard
Exactly-once No (at-least-once) FIFO queues: yes No No
Max message size 10 MB 256 KB 256 KB 1 MB
Pull/Push Both Pull Push (to SQS/Lambda/etc.) Pull

Cloud Tasks — Task Queue

Cloud Tasks = SQS but with better control over delivery. Designed for work offloading and rate-limited task dispatching. Best for: calling an API endpoint with rate limits, offloading slow work from request handlers.

SQS:        queue → consumer polls → process
Cloud Tasks: queue → Cloud Tasks delivers to → HTTP endpoint

Cloud Tasks pushes to an HTTP endpoint (your Cloud Run service, App Engine, or GCE endpoint). You don't poll.

# Create a queue
gcloud tasks queues create my-queue \
  --location=us-central1 \
  --max-dispatches-per-second=10 \    # rate limit (SQS has no equivalent)
  --max-concurrent-dispatches=5 \
  --max-attempts=5 \
  --min-backoff=10s \
  --max-backoff=300s
from google.cloud import tasks_v2
import json

client = tasks_v2.CloudTasksClient()
parent = client.queue_path("my-project", "us-central1", "my-queue")

# Create a task targeting a Cloud Run service
task = {
    "http_request": {
        "http_method": tasks_v2.HttpMethod.POST,
        "url": "https://my-worker.run.app/process",
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"order_id": "123"}).encode(),
        "oidc_token": {
            "service_account_email": "task-invoker@project.iam.gserviceaccount.com"
        }
    }
}

# Optional: schedule for the future
from google.protobuf import timestamp_pb2
import datetime
t = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
timestamp = timestamp_pb2.Timestamp()
timestamp.FromDatetime(t)
task["schedule_time"] = timestamp

response = client.create_task(request={"parent": parent, "task": task})
print(f"Task created: {response.name}")

Cloud Tasks vs Pub/Sub

Cloud Tasks Cloud Pub/Sub
Model Task queue → HTTP endpoint Topic → subscriptions
Fan-out No (one target) Yes
Rate limiting Yes (built-in) No
Future scheduling Yes No
Deduplication Yes (task names) No
Use case Offload work, call rate-limited APIs Fan-out events, async processing

Eventarc — Event Routing

Eventarc routes events from GCP services to Cloud Run or other targets. Equivalent to AWS EventBridge for GCP-native events.

graph LR
    classDef gcp fill:#4285f4,stroke:#2a56c6,color:#fff
    classDef orange fill:#e67e22,stroke:#d35400,color:#fff

    GCS["GCS object finalized<br/>(new file uploaded)"]:::gcp
    BQ["BigQuery job completed"]:::gcp
    AUDIT["Cloud Audit Log<br/>(any GCP API call)"]:::gcp
    EVENTARC["Eventarc"]:::gcp
    CR1["Cloud Run: image-processor"]:::orange
    CR2["Cloud Run: audit-logger"]:::orange

    GCS --> EVENTARC
    BQ --> EVENTARC
    AUDIT --> EVENTARC
    EVENTARC -->|"GCS events"| CR1
    EVENTARC -->|"Audit log events"| CR2
# Trigger Cloud Run when a new file lands in GCS
gcloud eventarc triggers create process-new-image \
  --location=us-central1 \
  --destination-run-service=image-processor \
  --destination-run-region=us-central1 \
  --event-filters="type=google.cloud.storage.object.v1.finalized" \
  --event-filters="bucket=my-uploads-bucket" \
  --service-account=eventarc-sa@project.iam.gserviceaccount.com

# Trigger on ANY GCP API call (Audit Log based)
gcloud eventarc triggers create on-bigquery-job \
  --location=us-central1 \
  --destination-run-service=bq-notifier \
  --destination-run-region=us-central1 \
  --event-filters="type=google.cloud.audit.log.v1.written" \
  --event-filters="serviceName=bigquery.googleapis.com" \
  --event-filters="methodName=google.cloud.bigquery.v2.JobService.InsertJob" \
  --service-account=eventarc-sa@project.iam.gserviceaccount.com

Receiving Events in Cloud Run

Eventarc delivers events as CloudEvents (CNCF standard format):

from cloudevents.http import from_http
from flask import Flask, request

app = Flask(__name__)

@app.route("/", methods=["POST"])
def handle_event():
    event = from_http(request.headers, request.data)

    print(f"Event type: {event['type']}")
    print(f"Event source: {event['source']}")

    # For GCS events
    if event["type"] == "google.cloud.storage.object.v1.finalized":
        data = event.data
        bucket = data["bucket"]
        name = data["name"]
        print(f"New file: gs://{bucket}/{name}")
        process_file(bucket, name)

    return "OK", 200

Eventarc vs AWS EventBridge

Eventarc AWS EventBridge
Event sources GCP services (Audit Logs, direct) 100+ AWS services + custom
Event format CloudEvents (CNCF standard) Custom JSON
Target types Cloud Run, Cloud Functions, GKE Lambda, SNS, SQS, Step Functions, 15+
Event replay No Yes (archive + replay)
Schema registry No Yes
Cross-account No Yes (event buses)
Cost Per event Per event ($1/million)

Workflow: Choosing the Right Messaging Service

I want to...

Process events from GCP services (GCS upload, BigQuery job, etc.)
→ Eventarc

Fan out to multiple consumers (email + analytics + notifications)
→ Pub/Sub topic with multiple subscriptions

Offload slow work from an HTTP handler, with rate limiting
→ Cloud Tasks

Simple job queue, consumer polls for work
→ Pub/Sub (pull subscription)

Kafka-compatible stream with ordering guarantees
→ Pub/Sub Lite (or Managed Kafka if you need exact Kafka compatibility)

Run something on a schedule
→ Cloud Scheduler