Skip to content

Fine Tuning PubSub Receive Performance

Alex Hong edited this page Feb 27, 2024 · 16 revisions

The PubSub client is a gRPC-based client with knobs for fine-tuning performance. We set reasonable defaults to these knobs, but we encourage users to adjust per their needs. This document describes some of these knobs and gives tips on setting them. This document is limited in scope to reception of messages and does not address publication.

Background

A few implementation details must be described to fully understand the settings:

As the PubSub client receives messages from the PubSub server, it puts them into a local buffer. The client hands messages from the buffer to the user by way of the callback function passed in to Receive. The user must Ack or Nack a message in this function. Each invocation by the client of the passed-in callback occurs in a goroutine; that is, messages are processed concurrently.

The buffer holds a maximum of MaxOutstandingMessages messages or MaxOutstandingBytes bytes, and the client stops requesting more messages from the server whenever the buffer is full. Messages in the buffer have an ack deadline; that is, the server keeps a deadline for each outstanding messages. When that deadline expires, the server considers the message lost and re-sends the message. Each message in the buffer automatically has their deadline periodically extended. If a message is held beyond its deadline - for example, if your program hangs - the message will be redelivered.

This medium post describes tuning Pub/Sub performance in more detail.

Settings

Subscription.ReceiveSettings.MaxExtension

This is the maximum amount of time that the client will extend a message's deadline. This value should be set as high as messages are expected to be processed, plus some buffer. It's fairly safe to set it quite high; the only downside is that it will take longer to recover from hanging programs. The higher the extension allowed, the longer it takes before the server considers messages lost and re-sends them to some other, healthy instance of your application.

Subscription.ReceiveSettings.MaxExtensionPeriod

This is the maximum amount of time to extend each message's deadline per ModifyAckDeadline RPC. Normally, the deadline is determined by the 99th percentile of previous message processing times. However, if normal processing times takes 10 minutes but an error occurs while processing a message within 1 minute, a message will be stuck and held by the client for the remaining 9 minutes. By setting the maximum amount of time to extend a message's deadline on a per-RPC basis, you can decrease the amount of time before message redelivery when errors occur. However the downside is that more ModifyAckDeadline RPCs will be sent.

Subscription.ReceiveSettings.MinExtensionPeriod

This is the minimum amount of time to extend each message's deadline per ModifyAckDeadline RPC. This is the complement setting of MaxExtensionPeriod and represents the lower bound of modack deadlines to send. If processing time is very low, we might want to issue less frequent ModifyAckDeadline RPCs rather than every 10 seconds. Setting both MinExtensionPeriod and MaxExtensionPeriod to the same value effectively removes the automatic derivation of deadlines and fixes it to the value you wish to extend your messages deadlines by each time.

Subscription.ReceiveSettings.MaxOutstandingMessages

This is the maximum amount of messages that are to be processed by the callback function at a time. Once this limit is reached, the client waits for messages to be acked or nacked by the callback before requesting more messages from the server.

This value is set by default to a fairly conservatively low number. We strongly encourage setting this number as high as memory allows, since a low setting will artificially rate limit reception. Setting this value to -1 causes it to be unbounded.

Subscription.ReceiveSettings.MaxOutstandingBytes

This is the maximum amount of bytes (message size) that are to be processed by the callback function at a time. Once this limit is reached, the client waits for messages to be acked or nacked by the callback before requesting more messages from the server.

Note that there sometimes can be more bytes pulled and processing than MaxOutstandingBytes allows. This is due to the fact that the server doesn't consider byte sizes when provisioning server side flow control. For example, if the client sets MaxOutstandingBytes to 50 KiB, but receives a batch of messages totaling 100 KiB, there will be a temporary overflow of message byte size until messages are acked.

Similar to MaxOutstandingMessages, we recommend setting this higher to maximize processing throughput. Setting this value to -1 causes it to be unbounded.

Subscription.ReceiveSettings.NumGoroutines

This is the number of goroutines spawned to receive messages from the Pubsub server, where each goroutine opens a StreamingPull stream. This setting affects the rate of message intake from server to local buffer.

The default value of 10 is sufficient for most workloads. Each stream can handle about 10 MB/s of messages, allowing for approximately 100 MB/s. This is the upper bound and assuming that messages are perfectly distributed across each stream. However, if your message throughput is lower than this, we recommend experimenting with a lower amount by starting at 1 and increasing from there. Reducing the number of streams can improve the performance as it reduces the changes of I/O bounding the application.

If you have a higher throughput requirement, we generally do not recommend going above 100 NumGoroutines/streams. This can lead to adverse effects such as acks/modacks not succeeding in a reasonable amount of time, leading to message expiration. In these cases, we recommend horizontally scaling by increasing the number of subscriber client applications.

Note, if using the deprecated synchronous mode, NumGoroutines is capped at 1.

Subscription.ReceiveSettings.Synchronous [DEPRECATED]

This configures the underlying message receiving mechanism. By default, we recommend turning off Synchronous mode, as this uses the streaming pull RPC. The streaming RPC has the benefit of reducing overhead compared to synchronous pull, which issues a Pull RPC every time new messages need to be pulled. In contrast, streaming pull periodically receives messages from the Pub/Sub server over the long-lived stream. The downside is that with streaming pull, the client does not have the fine grained control over how many messages are sent and it could exceed MaxOutstandingMessages.

General tips

Each application should use a single PubSub client instead of creating many. In addition, when publishing to a single topic, topic should be instantiated once and reused to take advantage of flow control and batching capabilities.