---
title: "Pub/Sub oder Eventarc? Event-Driven GCP ohne Spaghetti"
description: "Wann Pub/Sub, wann Eventarc — und wie du ein event-driven GCP-System sauber hältst: Idempotenz, Dead-Letter-Queues und die Falle des Fan-out-Spaghetti."
author: Aleksei Aleinikov
date: 2026-06-29
lang: de
tags: [event-driven, pub-sub, eventarc, gcp, messaging, architecture, cloud-events]
canonical: https://www.alekseialeinikov.com/de/blog/topics/architecture/pubsub-oder-eventarc-2026-event-driven-gcp-ohne-spaghetti
source: alekseialeinikov.com
---

# Pub/Sub oder Eventarc? Event-Driven GCP ohne Spaghetti

Das erste event-driven System fühlt sich wie Magie an: eine Datei landet im Bucket, eine Function feuert, ein Datensatz erscheint. Das zehnte fühlt sich wie ein Tatort an. Niemand weiß, was was auslöst, ein Duplikat hat gerade einen Kunden doppelt belastet, und eine schlechte Nachricht wiederholt sich seit Dienstag.

Event-driven auf GCP ist nicht schwer, weil die Tools schwach wären. Es entgleist, weil zwei ähnlich aussehende Dienste — **Pub/Sub** und **Eventarc** — austauschbar genutzt werden und die langweiligen Teile (Idempotenz, Dead-Letter) übersprungen werden, bis die Produktion sie lehrt.

![Wenn die Datei landet, die Function feuert und niemand erklären kann, warum.](https://www.alekseialeinikov.com/blog/pubsub-eventarc-2026.webp)

Das ist der Feldführer: zu welchem du greifst, wie du Handler sicher hältst und wie du verhinderst, dass der Fan-out zu Spaghetti wird.

> Routing ist die eine Hälfte einer soliden GCP-Plattform; sie abzusichern die andere. Für die Sicherheitsgrundlinie, auf der deine Cluster laufen, siehe [Secure-by-Default GKE: Eine Referenzarchitektur für 2026](https://www.alekseialeinikov.com/de/blog/topics/architecture/secure-by-default-gke-referenzarchitektur-2026).

## Pub/Sub vs Eventarc: einer transportiert, einer routet

Sie überlappen, also wird aus Gewohnheit gewählt. Die saubere Regel:

- **Pub/Sub** ist das Messaging-Rückgrat. Du publizierst Events, die *dir* gehören, in ein Topic; Consumer abonnieren. Du kontrollierst Schema, Ordering, Retention. Nutze es für eigene Domain-Events — `order.placed`, `payment.settled`.
- **Eventarc** ist ein Router darüber. Es nimmt Events, die Google *bereits* aussendet — Cloud-Storage-Uploads, Audit Logs, 90+ Quellen — und liefert sie als einen CloudEvents-Vertrag an ein Ziel. Nutze es, um auf die Plattform zu *reagieren*, ohne Glue zu schreiben.

Selten wählst du eines gegen das andere; Eventarc reitet ohnehin auf Pub/Sub. Die Frage ist „wer erzeugt dieses Event?". Bist du es, Pub/Sub. Ist es Google, Eventarc.

![Event-driven GCP auf einen Blick: Google sendet an Eventarc, du publizierst an Pub/Sub, Fan-out zu idempotenten Subscribern, Dead-Letter für das Gift.](https://www.alekseialeinikov.com/blog/pubsub-eventarc-flow-2026.webp)

```bash
# Eigenes Event → Pub/Sub
gcloud pubsub topics create order-placed
gcloud pubsub subscriptions create fulfilment --topic=order-placed

# Ein GCP-Event (Datei hochgeladen) → Eventarc, kein Glue-Code
gcloud eventarc triggers create on-upload \
  --destination-run-service=thumbnailer \
  --event-filters="type=google.cloud.storage.object.v1.finalized" \
  --event-filters="bucket=my-uploads"
```

## At-least-once heißt: es kommt zweimal an

Beide liefern **at-least-once**. Das ist kein Sonderfall — plane dafür. Ein Retry, ein langsames Ack, ein Redeploy mittendrin, und dasselbe Event landet erneut. Wenn „zweimal" eine Doppelbelastung bedeutet, ist das ein Bug, den du geschrieben hast, kein Pech.

Mache jeden Consumer idempotent: dedupliziere über eine stabile Event-ID, dann wird ein Re-Run zum No-op.

```python
def handle(event):
    eid = event["id"]                 # stabil, von Pub/Sub/Eventarc geliefert
    if seen.add_if_absent(eid):       # atomar: False, wenn schon vorhanden
        process(event)                # echte Arbeit genau einmal
    # Duplikat → still acken, kein Seiteneffekt
```

Der Store kann Redis mit TTL, eine Unique-Spalte oder Firestore sein. Die Regel bleibt: die zweite Kopie tut nichts.

## Dead-Letter: eine Gift-Nachricht darf nicht alles stoppen

Ein einzelner unparsbarer Payload wiederholt sich ewig und blockiert die Schlange dahinter. Ein Dead-Letter-Topic parkt ihn nach N Versuchen, damit echter Traffic weiterläuft.

```bash
gcloud pubsub subscriptions update fulfilment \
  --dead-letter-topic=fulfilment-dead \
  --max-delivery-attempts=5
```

Dann alarmiere auf dem Dead-Letter-Topic. Eine Nachricht dort ist eine zu beantwortende Frage, kein Rauschen — aber der Rest des Flows stockt nie.

## Push oder Pull: wie Subscriber ihre Events bekommen

Vor der Topologie: das Liefermodell richtig wählen. Eine Pub/Sub-Subscription ist entweder **Push** oder **Pull**, und Einsteiger wählen falsch und kämpfen dann gegen die Plattform:

- **Push** — Pub/Sub POSTet jede Nachricht an einen HTTPS-Endpoint (Cloud Run, Function). Kein Polling, skaliert auf null, ideal für Serverless. Der Endpoint muss schnell mit 2xx acken.
- **Pull** — dein Worker holt Batches und ackt sie. Am besten für Hochdurchsatz-Backends, lange Jobs oder feine Concurrency-Kontrolle.

Faustregel: Serverless-Ziel → Push; Dauer-Worker → Pull. Eventarc zu Cloud Run ist intern Push — du wählst nicht, und das ist gut so.

## Ordering: meist willst du es nicht

Pub/Sub kann Reihenfolge *innerhalb eines Ordering-Keys* halten, aber Ordering drosselt Durchsatz und die meisten Flows brauchen es nicht. Baue Consumer so, dass die Ankunftsreihenfolge egal ist: `order.placed` dann `order.cancelled` muss in jeder Reihenfolge korrekt sein. Aktiviere Ordering-Keys nur, wenn State wirklich von Sequenz abhängt (Konto-Ledger), und keye pro Entität, nie global.

```bash
gcloud pubsub topics publish order-events --message="..." --ordering-key="acct-42"
```

## Publiziere einen Fakt, kein Blob

Ein Topic ist ein Vertrag. Events sollten versioniert, selbstbeschreibend und klein sein — IDs, keine ganzen Datensätze. Trage `type`, eine stabile `id` und ein `v`, damit Consumer ohne Koordination wachsen:

```json
{ "id": "evt_9f3", "type": "order.placed", "v": 1, "orderId": "A42", "ts": "2026-06-29T10:00:00Z" }
```

Fette Payloads koppeln Producer und Consumer; die ID lässt jeden Subscriber genau holen, was er braucht. Felder frei ergänzen, nie brechen oder umbenennen — stattdessen `v` erhöhen.

## Wann NICHT event-driven

Events sind nicht gratis, und die schlimmste Spaghetti ist async, wo sync genügt. Greife zu einem direkten Call oder **Cloud Tasks**, wenn:

- Du *jetzt* ein Ergebnis brauchst — eine synchrone Anfrage, kein Broadcast.
- Ein bekannter Producer, ein bekannter Consumer — das ist eine Queue; nimm **Cloud Tasks** mit Retries und Rate-Limits, kein Topic.
- Ein Zwei-Schritt-Flow — ein direkter Call schlägt ein Topic, das niemand sonst abonniert.

Nutze Pub/Sub, wenn *viele* Consumer denselben Fakt brauchen und Producer nicht wissen dürfen, wer lauscht. Tut es nur einer, hast du Spaghetti umsonst gekauft.

## Die Spaghetti: hör auf, Trigger blind zu verketten

Das echte Chaos sind nicht die Tools — es ist die Topologie. A feuert B, B feuert C, C feuert leise wieder A, und niemand hat die Karte gezeichnet. Drei Regeln halten es flach:

1. **Topic = Fakt, nicht Befehl.** Publiziere `order.placed`, nicht `send.email`. Producer sagen, was geschah; Consumer entscheiden, was zu tun ist. Neues Verhalten = neuer Subscriber, null Producer-Änderungen.
2. **Fan-out statt Kette.** Viele Subscriber an einem Topic schlagen ein 6-Hop-Relay. Parallele Pfade sind debugbar; Ketten verstecken Zyklen.
3. **Eine Routing-Quelle.** Halte Trigger in Terraform, nicht im Klick. Lebt der Graph nur im Kopf, ist es schon Spaghetti.

## Die Feldregel

Pub/Sub, wenn du das Event erzeugst, Eventarc, wenn Google es tut. Nimm an, jedes Event kommt zweimal, und mache Handler idempotent. Dead-letter das Gift, damit eine schlechte Nachricht die Linie nicht stoppt. Publiziere Fakten, mach Fan-out statt Kette und halte Routing im Code. Das richtig gemacht, bleibt event-driven die Magie von Tag eins — statt zum Tatort von Artikel zehn zu werden.
