GCP Pub/Sub Subscription Analyzer

Read a Pub/Sub subscription for the settings behind duplicate delivery and lost messages: an ack deadline shorter than the work, a dead letter policy missing the two IAM grants it needs, and an expiration policy that deletes the subscription itself.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Check the subscription. Nothing leaves this tab.

Wanted a different tool?

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

The ten second default

Past it the message is redelivered while the first attempt is still running, which is what most duplicate delivery actually is

{"name":"projects/p/subscriptions/orders","topic":"projects/p/topics/orders","ackDeadlineSeconds":10,"messageRetentionDuration":"604800s"}

A dead letter policy with no permissions

Two IAM grants are needed and neither is created for you. Without them the policy is visible in the console and inert.

{"name":"projects/p/subscriptions/orders","topic":"projects/p/topics/orders","ackDeadlineSeconds":120,"messageRetentionDuration":"604800s","deadLetterPolicy":{"deadLetterTopic":"projects/p/topics/orders-dead","maxDeliveryAttempts":5}}

A push endpoint anyone can post to

Without an OIDC token the endpoint cannot tell a Pub/Sub delivery from anyone else, and a push URL is not a secret

{"name":"projects/p/subscriptions/hook","topic":"projects/p/topics/events","ackDeadlineSeconds":60,"messageRetentionDuration":"604800s","pushConfig":{"pushEndpoint":"https://api.example.com/pubsub"}}

The policy that deletes the subscription

The expiration policy removes the SUBSCRIPTION after 31 days of inactivity, so a consumer down longer loses every message published meanwhile

{"name":"projects/p/subscriptions/rare","topic":"projects/p/topics/rare","ackDeadlineSeconds":60,"messageRetentionDuration":"604800s","expirationPolicy":{"ttl":"2678400s"}}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Leaving the ack deadline at ten seconds

    Past it the message is redelivered while the first attempt is still running, so the work happens twice and neither copy knows. Client libraries extend it automatically, but only up to their own maximum extension, which is the limit that actually applies and the one nobody sets.

    Instead:Raise the deadline above the p99 of your processing time, up to 600 seconds, and set the library's maxExtension to match.

  2. Configuring a dead letter topic and stopping there

    The Pub/Sub service agent needs roles/pubsub.publisher on the dead letter topic and roles/pubsub.subscriber on the subscription. Without both the policy is inert and messages redeliver forever, and nothing reports it because the policy is valid.

    Instead:Grant both to service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com, and subscribe something to the dead letter topic so the messages are read rather than expiring there.

  3. Assuming a push endpoint gets the same deadline handling as pull

    With push, the HTTP response IS the ack, and nothing extends anything. Slow processing means the delivery is treated as failed and retried, so the endpoint runs the same work several times.

    Instead:Return quickly and process asynchronously, or use pull, where the client library holds the lease while processing continues.

  4. Enabling message ordering without ordering keys

    Ordering applies only to messages published with a key, and only within that key. A publisher that sets no key gets no ordering while the subscription pays the throughput cost, and within a key one stuck message blocks every later message for that key.

    Instead:Confirm the publisher sets keys, and pick keys with enough cardinality that one failure does not stop a meaningful share of the traffic.

  5. Leaving the default expiration policy on a subscription that matters

    It deletes the SUBSCRIPTION after 31 days of inactivity, not the messages. A consumer down longer comes back to find nothing to connect to, and everything published in the meantime is unrecoverable.

    Instead:Set the expiration policy TTL to empty. It is a cleanup mechanism for abandoned subscriptions and not a retention policy.

Duplicate delivery is almost always the ack deadline

Pub/Sub gets blamed for redelivering messages, and the cause is usually a ten second promise nobody chose and a client library limit nobody set.

The default ack deadline is ten seconds

Past it the message is redelivered while the first attempt is still running, so the work happens twice and neither copy knows about the other. Client libraries extend the deadline automatically while processing continues, which is why this often works, and only up to their own maximum extension, which is a setting most people have never touched. That limit is what actually applies, not the number on the subscription.

ackDeadlineSeconds     10 by default, 600 maximum

Python:  flow_control / subscriber lease management
Go:      ReceiveSettings.MaxExtension
Java:    setMaxAckExtensionPeriod

A dead letter topic needs two IAM grants and nothing creates them

The Pub/Sub service agent must have roles/pubsub.publisher on the dead letter topic AND roles/pubsub.subscriber on this subscription. Without both, the policy is configured, shows in the console, and does nothing: messages redeliver forever instead of moving. Nothing reports it, because the policy itself is valid. The service agent address uses the project NUMBER rather than the ID, which is one more thing to get wrong.

service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com

roles/pubsub.publisher   on the dead letter topic
roles/pubsub.subscriber  on the subscription

A dead letter topic nobody reads is a slower way to lose the message

Messages arrive and expire unread. Subscribe something to it, even if that something only writes to a log, or the policy converts an infinite retry into a silent drop seven days later.

With push, the HTTP response is the ack

There is no client library extending the deadline for you, so slow processing means the delivery is treated as failed and retried, and the endpoint runs the same work several times. A push endpoint also needs an OIDC token: without one it accepts a POST from anyone who knows the URL, and a push URL is not a secret, it is in your configuration, your Terraform state and your console.

Ordering is per key, and it is a queue per key

It applies only to messages published WITH an ordering key, so a publisher that does not set one gets no ordering while the subscription pays for it. Within a key delivery is serialised, so one slow message blocks every later message with the same key, and one message that keeps failing blocks that key until it dead-letters. Choose keys with enough cardinality that a stuck message does not stop a meaningful share of the traffic.

The expiration policy deletes the subscription, not the messages

The default is 31 days of inactivity. A consumer down longer than that comes back to find nothing to connect to, and every message published while it was gone is unrecoverable, because the subscription that would have held them no longer exists. It is a cleanup mechanism for abandoned subscriptions and it is not a retention policy, and for anything that matters the TTL should be empty.

What this cannot see

The two things that decide whether the configuration works are outside it. The IAM grants a dead letter policy needs are in a policy on another resource, and the maxExtension your client library applies to the ack deadline is in your code. This reads the subscription, which tells you what was asked for rather than what will happen. It also cannot see the topic's own retention, which is separate and shorter by default, or whether anything is subscribed to the dead letter topic.