Support configurable MaxOutstanding* and NumGoroutines settings for GCP PubSub component (#3442)
Signed-off-by: Nathan Lowry <nathandl@gmail.com>
This commit is contained in:
parent
0375e200b6
commit
864baaad3d
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
Thank you for your interest in Dapr!
|
Thank you for your interest in Dapr!
|
||||||
|
|
||||||
This project welcomes contributions and suggestions. Most contributions require you to signoff on your commits via
|
This project welcomes contributions and suggestions. Most contributions require you to signoff on your commits via
|
||||||
the Developer Certificate of Origin (DCO). When you submit a pull request, a DCO-bot will automatically determine
|
the Developer Certificate of Origin (DCO). When you submit a pull request, a DCO-bot will automatically determine
|
||||||
whether you need to provide signoff for your commit. Please follow the instructions provided by DCO-bot, as pull
|
whether you need to provide signoff for your commit. Please follow the instructions provided by DCO-bot, as pull
|
||||||
requests cannot be merged until the author(s) have provided signoff to fulfill the DCO requirement.
|
requests cannot be merged until the author(s) have provided signoff to fulfill the DCO requirement.
|
||||||
You may find more information on the DCO requirements [below](#developer-certificate-of-origin-signing-your-work).
|
You may find more information on the DCO requirements [below](#developer-certificate-of-origin-signing-your-work).
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ All contributions come through pull requests. To submit a proposed change, we re
|
||||||
|
|
||||||
#### Use work-in-progress PRs for early feedback
|
#### Use work-in-progress PRs for early feedback
|
||||||
|
|
||||||
A good way to communicate before investing too much time is to create a "Work-in-progress" PR and share it with your reviewers. The standard way of doing this is to add a "[WIP]" prefix in your PR's title and assign the **do-not-merge** label. This will let people looking at your PR know that it is not well baked yet.
|
A good way to communicate before investing too much time is to create a "Work-in-progress" PR and share it with your reviewers. The standard way of doing this is to open your PR as a draft, add a "[WIP]" prefix in your PR's title, and assign the **do-not-merge** label. This will let people looking at your PR know that it is not well baked yet.
|
||||||
|
|
||||||
### Developer Certificate of Origin: Signing your work
|
### Developer Certificate of Origin: Signing your work
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,15 @@ type metadata struct {
|
||||||
AuthProviderCertURL string `mapstructure:"authProviderX509CertUrl" mdignore:"true"`
|
AuthProviderCertURL string `mapstructure:"authProviderX509CertUrl" mdignore:"true"`
|
||||||
ClientCertURL string `mapstructure:"clientX509CertUrl" mdignore:"true"`
|
ClientCertURL string `mapstructure:"clientX509CertUrl" mdignore:"true"`
|
||||||
|
|
||||||
DisableEntityManagement bool `mapstructure:"disableEntityManagement"`
|
DisableEntityManagement bool `mapstructure:"disableEntityManagement"`
|
||||||
EnableMessageOrdering bool `mapstructure:"enableMessageOrdering"`
|
EnableMessageOrdering bool `mapstructure:"enableMessageOrdering"`
|
||||||
MaxReconnectionAttempts int `mapstructure:"maxReconnectionAttempts"`
|
MaxReconnectionAttempts int `mapstructure:"maxReconnectionAttempts"`
|
||||||
ConnectionRecoveryInSec int `mapstructure:"connectionRecoveryInSec"`
|
ConnectionRecoveryInSec int `mapstructure:"connectionRecoveryInSec"`
|
||||||
ConnectionEndpoint string `mapstructure:"endpoint"`
|
ConnectionEndpoint string `mapstructure:"endpoint"`
|
||||||
OrderingKey string `mapstructure:"orderingKey"`
|
OrderingKey string `mapstructure:"orderingKey"`
|
||||||
DeadLetterTopic string `mapstructure:"deadLetterTopic"`
|
DeadLetterTopic string `mapstructure:"deadLetterTopic"`
|
||||||
MaxDeliveryAttempts int `mapstructure:"maxDeliveryAttempts"`
|
MaxDeliveryAttempts int `mapstructure:"maxDeliveryAttempts"`
|
||||||
|
MaxOutstandingMessages int `mapstructure:"maxOutstandingMessages"`
|
||||||
|
MaxOutstandingBytes int `mapstructure:"maxOutstandingBytes"`
|
||||||
|
MaxConcurrentConnections int `mapstructure:"maxConcurrentConnections"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,32 +13,32 @@ builtinAuthenticationProfiles:
|
||||||
metadata:
|
metadata:
|
||||||
- name: enableMessageOrdering
|
- name: enableMessageOrdering
|
||||||
description: |
|
description: |
|
||||||
When set to "true", subscribed messages will be received in order,
|
When set to "true", subscribed messages will be received in order,
|
||||||
depending on publishing and permissions configuration.
|
depending on publishing and permissions configuration.
|
||||||
type: bool
|
type: bool
|
||||||
default: 'false'
|
default: 'false'
|
||||||
example: '"true", "false"'
|
example: '"true", "false"'
|
||||||
- name: orderingKey
|
- name: orderingKey
|
||||||
description: |
|
description: |
|
||||||
The key provided in the request. It's used when "enableMessageOrdering"
|
The key provided in the request. It's used when "enableMessageOrdering"
|
||||||
is set to true to order messages based on such key.
|
is set to true to order messages based on such key.
|
||||||
type: string
|
type: string
|
||||||
example: '"my-orderingkey"'
|
example: '"my-orderingkey"'
|
||||||
- name: disableEntityManagement
|
- name: disableEntityManagement
|
||||||
description: |
|
description: |
|
||||||
When set to true, topics and subscriptions do not get created automatically.
|
When set to true, topics and subscriptions do not get created automatically.
|
||||||
type: bool
|
type: bool
|
||||||
default: 'false'
|
default: 'false'
|
||||||
example: '"true", "false"'
|
example: '"true", "false"'
|
||||||
- name: maxReconnectionAttempts
|
- name: maxReconnectionAttempts
|
||||||
description: |
|
description: |
|
||||||
Defines the maximum number of reconnect attempts.
|
Defines the maximum number of reconnect attempts.
|
||||||
type: number
|
type: number
|
||||||
default: '30'
|
default: '30'
|
||||||
example: '30'
|
example: '30'
|
||||||
- name: connectionRecoveryInSec
|
- name: connectionRecoveryInSec
|
||||||
description: |
|
description: |
|
||||||
Time in seconds to wait between connection recovery attempts.
|
Time in seconds to wait between connection recovery attempts.
|
||||||
type: number
|
type: number
|
||||||
default: '2'
|
default: '2'
|
||||||
example: '2'
|
example: '2'
|
||||||
|
@ -60,3 +60,18 @@ metadata:
|
||||||
type: number
|
type: number
|
||||||
default: '5'
|
default: '5'
|
||||||
example: '5'
|
example: '5'
|
||||||
|
- name: maxOutstandingMessages
|
||||||
|
description: |
|
||||||
|
Maximum number of messages a GCP streaming-pull connection is allowed to have outstanding
|
||||||
|
type: number
|
||||||
|
example: '1000'
|
||||||
|
- name: maxOutstandingBytes
|
||||||
|
description: |
|
||||||
|
Maximum number of bytes a GCP streaming-pull connection is allowed to have outstanding
|
||||||
|
type: number
|
||||||
|
example: '1e9'
|
||||||
|
- name: maxConcurrentConnections
|
||||||
|
description: |
|
||||||
|
Max number of concurrent streaming-pull connections to maintain
|
||||||
|
type: number
|
||||||
|
example: '10'
|
||||||
|
|
|
@ -319,6 +319,21 @@ func (g *GCPPubSub) handleSubscriptionMessages(parentCtx context.Context, topic
|
||||||
|
|
||||||
readReconnectAttemptsRemaining := func() int { return len(reconnAttempts) }
|
readReconnectAttemptsRemaining := func() int { return len(reconnAttempts) }
|
||||||
|
|
||||||
|
// Apply configured limits for MaxOutstandingMessages, MaxOutstandingBytes, and MaxConcurrentConnections
|
||||||
|
// NOTE: negative MaxOutstandingMessages and MaxOutstaningBytes values are allowed and indicate
|
||||||
|
// in the GCP pubsub library that no limit should be applied. Zero values result in the package
|
||||||
|
// default being used: 1000 messages and 1e9 (1G) bytes respectively.
|
||||||
|
if g.metadata.MaxOutstandingMessages != 0 {
|
||||||
|
sub.ReceiveSettings.MaxOutstandingMessages = g.metadata.MaxOutstandingMessages
|
||||||
|
}
|
||||||
|
if g.metadata.MaxOutstandingBytes != 0 {
|
||||||
|
sub.ReceiveSettings.MaxOutstandingBytes = g.metadata.MaxOutstandingBytes
|
||||||
|
}
|
||||||
|
// NOTE: For MaxConcurrentConnections, negative values are not allowed so only override if the value is greater than 0
|
||||||
|
if g.metadata.MaxConcurrentConnections > 0 {
|
||||||
|
sub.ReceiveSettings.NumGoroutines = g.metadata.MaxConcurrentConnections
|
||||||
|
}
|
||||||
|
|
||||||
// Periodically refill the reconnect attempts channel to avoid
|
// Periodically refill the reconnect attempts channel to avoid
|
||||||
// exhausting all the refill attempts due to intermittent issues
|
// exhausting all the refill attempts due to intermittent issues
|
||||||
// occurring over a longer period of time.
|
// occurring over a longer period of time.
|
||||||
|
|
|
@ -128,4 +128,133 @@ func TestInit(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.ErrorContains(t, err, "connectionRecoveryInSec")
|
require.ErrorContains(t, err, "connectionRecoveryInSec")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("valid optional maxOutstandingMessages", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
"maxOutstandingMessages": "50",
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := createMetadata(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 50, md.MaxOutstandingMessages, "MaxOutstandingMessages should match the provided configuration")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing optional maxOutstandingMessages", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := createMetadata(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, md.MaxOutstandingMessages)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid negative optional maxOutstandingMessages", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
"maxOutstandingMessages": "-1",
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := createMetadata(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, -1, md.MaxOutstandingMessages, "MaxOutstandingMessages should match the provided configuration")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid optional maxOutstandingMessages", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
"maxOutstandingMessages": "foobar",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := createMetadata(m)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "maxOutstandingMessages")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid optional maxOutstandingBytes", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
"maxOutstandingBytes": "1000000000",
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := createMetadata(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1000000000, md.MaxOutstandingBytes, "MaxOutstandingBytes should match the provided configuration")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing optional maxOutstandingBytes", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := createMetadata(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, md.MaxOutstandingBytes)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid negative optional maxOutstandingBytes", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
"maxOutstandingBytes": "-1",
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := createMetadata(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, -1, md.MaxOutstandingBytes, "MaxOutstandingBytes should match the provided configuration")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid optional maxOutstandingBytes", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
"maxOutstandingBytes": "foobar",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := createMetadata(m)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "maxOutstandingBytes")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid optional maxConcurrentConnections", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
"maxConcurrentConnections": "2",
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := createMetadata(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, md.MaxConcurrentConnections, "MaxConcurrentConnections should match the provided configuration")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing optional maxConcurrentConnections", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
}
|
||||||
|
|
||||||
|
md, err := createMetadata(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, md.MaxConcurrentConnections)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid optional maxConcurrentConnections", func(t *testing.T) {
|
||||||
|
m := pubsub.Metadata{}
|
||||||
|
m.Properties = map[string]string{
|
||||||
|
"projectId": "test-project",
|
||||||
|
"maxConcurrentConnections": "foobar",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := createMetadata(m)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "maxConcurrentConnections")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue