From cbb9e2d3f04e06365bfe42769f02c8b667ce531d Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Mon, 8 May 2023 19:03:38 +0200 Subject: [PATCH] [chore/performance] Make sender multiplier configurable (#1750) --- docs/configuration/advanced.md | 32 ++++++++++++++++++++++++++++++++ example/config.yaml | 32 ++++++++++++++++++++++++++++++++ internal/config/config.go | 1 + internal/config/defaults.go | 1 + internal/config/flags.go | 1 + internal/config/helpers.gen.go | 25 +++++++++++++++++++++++++ internal/transport/controller.go | 19 ++++++++++++++----- test/envparsing.sh | 2 ++ testrig/config.go | 1 + 9 files changed, 109 insertions(+), 5 deletions(-) diff --git a/docs/configuration/advanced.md b/docs/configuration/advanced.md index 7cf962903..6bf57f869 100644 --- a/docs/configuration/advanced.md +++ b/docs/configuration/advanced.md @@ -79,4 +79,36 @@ advanced-rate-limit-requests: 300 # Examples: [8, 4, 9, 0] # Default: 8 advanced-throttling-multiplier: 8 + +# Int. CPU multiplier for the amount of goroutines to spawn in order to send messages via ActivityPub. +# Messages will be batched so that at most multiplier * CPU count messages will be sent out at once. +# This can be tuned to limit concurrent POSTing to remote inboxes, preventing your instance CPU +# usage from skyrocketing when an account with many followers posts a new status. +# +# Messages are split among available senders, and each sender processes its assigned messages in serial. +# For example, say a user with 1000 followers is on an instance with 2 CPUs. With the default multiplier +# of 2, this means 4 senders would be in process at once on this instance. When the user creates a new post, +# each sender would end up iterating through about 250 Create messages + delivering them to remote instances. +# +# If you set this to 0 or less, only 1 sender will be used regardless of CPU count. This may be +# useful in cases where you are working with very tight network or CPU constraints. +# +# Example values for multiplier 2 (default): +# +# 1 cpu = 2 concurrent senders +# 2 cpu = 4 concurrent senders +# 4 cpu = 8 concurrent senders +# +# Example values for multiplier 4: +# +# 1 cpu = 4 concurrent senders +# 2 cpu = 8 concurrent senders +# 4 cpu = 16 concurrent senders +# +# Example values for multiplier <1: +# +# 1 cpu = 1 concurrent sender +# 2 cpu = 1 concurrent sender +# 4 cpu = 1 concurrent sender +advanced-sender-multiplier: 2 ``` diff --git a/example/config.yaml b/example/config.yaml index 1bba4dad4..598e29168 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -848,3 +848,35 @@ advanced-throttling-multiplier: 8 # Examples: [30s, 10s, 5s, 1m] # Default: 30s advanced-throttling-retry-after: "30s" + +# Int. CPU multiplier for the amount of goroutines to spawn in order to send messages via ActivityPub. +# Messages will be batched so that at most multiplier * CPU count messages will be sent out at once. +# This can be tuned to limit concurrent POSTing to remote inboxes, preventing your instance CPU +# usage from skyrocketing when an account with many followers posts a new status. +# +# Messages are split among available senders, and each sender processes its assigned messages in serial. +# For example, say a user with 1000 followers is on an instance with 2 CPUs. With the default multiplier +# of 2, this means 4 senders would be in process at once on this instance. When the user creates a new post, +# each sender would end up iterating through about 250 Create messages + delivering them to remote instances. +# +# If you set this to 0 or less, only 1 sender will be used regardless of CPU count. This may be +# useful in cases where you are working with very tight network or CPU constraints. +# +# Example values for multiplier 2 (default): +# +# 1 cpu = 2 concurrent senders +# 2 cpu = 4 concurrent senders +# 4 cpu = 8 concurrent senders +# +# Example values for multiplier 4: +# +# 1 cpu = 4 concurrent senders +# 2 cpu = 8 concurrent senders +# 4 cpu = 16 concurrent senders +# +# Example values for multiplier <1: +# +# 1 cpu = 1 concurrent sender +# 2 cpu = 1 concurrent sender +# 4 cpu = 1 concurrent sender +advanced-sender-multiplier: 2 diff --git a/internal/config/config.go b/internal/config/config.go index ab353f32a..a1570bbaf 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -141,6 +141,7 @@ type Configuration struct { AdvancedRateLimitRequests int `name:"advanced-rate-limit-requests" usage:"Amount of HTTP requests to permit within a 5 minute window. 0 or less turns rate limiting off."` AdvancedThrottlingMultiplier int `name:"advanced-throttling-multiplier" usage:"Multiplier to use per cpu for http request throttling. 0 or less turns throttling off."` AdvancedThrottlingRetryAfter time.Duration `name:"advanced-throttling-retry-after" usage:"Retry-After duration response to send for throttled requests."` + AdvancedSenderMultiplier int `name:"advanced-sender-multiplier" usage:"Multiplier to use per cpu for batching outgoing fedi messages. 0 or less turns batching off (not recommended)."` // Cache configuration vars. Cache CacheConfiguration `name:"cache"` diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 999b81c65..1dc446e4c 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -116,6 +116,7 @@ AdvancedCookiesSamesite: "lax", AdvancedRateLimitRequests: 300, // 1 per second per 5 minutes AdvancedThrottlingMultiplier: 8, // 8 open requests per CPU + AdvancedSenderMultiplier: 2, // 2 senders per CPU Cache: CacheConfiguration{ GTS: GTSCacheConfiguration{ diff --git a/internal/config/flags.go b/internal/config/flags.go index e9925ded0..c9899b67e 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -144,6 +144,7 @@ func (s *ConfigState) AddServerFlags(cmd *cobra.Command) { cmd.Flags().Int(AdvancedRateLimitRequestsFlag(), cfg.AdvancedRateLimitRequests, fieldtag("AdvancedRateLimitRequests", "usage")) cmd.Flags().Int(AdvancedThrottlingMultiplierFlag(), cfg.AdvancedThrottlingMultiplier, fieldtag("AdvancedThrottlingMultiplier", "usage")) cmd.Flags().Duration(AdvancedThrottlingRetryAfterFlag(), cfg.AdvancedThrottlingRetryAfter, fieldtag("AdvancedThrottlingRetryAfter", "usage")) + cmd.Flags().Int(AdvancedSenderMultiplierFlag(), cfg.AdvancedSenderMultiplier, fieldtag("AdvancedSenderMultiplier", "usage")) cmd.Flags().String(RequestIDHeaderFlag(), cfg.RequestIDHeader, fieldtag("RequestIDHeader", "usage")) }) diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go index e35eb0665..236d1ea36 100644 --- a/internal/config/helpers.gen.go +++ b/internal/config/helpers.gen.go @@ -2124,6 +2124,31 @@ func GetAdvancedThrottlingRetryAfter() time.Duration { return global.GetAdvanced // SetAdvancedThrottlingRetryAfter safely sets the value for global configuration 'AdvancedThrottlingRetryAfter' field func SetAdvancedThrottlingRetryAfter(v time.Duration) { global.SetAdvancedThrottlingRetryAfter(v) } +// GetAdvancedSenderMultiplier safely fetches the Configuration value for state's 'AdvancedSenderMultiplier' field +func (st *ConfigState) GetAdvancedSenderMultiplier() (v int) { + st.mutex.Lock() + v = st.config.AdvancedSenderMultiplier + st.mutex.Unlock() + return +} + +// SetAdvancedSenderMultiplier safely sets the Configuration value for state's 'AdvancedSenderMultiplier' field +func (st *ConfigState) SetAdvancedSenderMultiplier(v int) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.AdvancedSenderMultiplier = v + st.reloadToViper() +} + +// AdvancedSenderMultiplierFlag returns the flag name for the 'AdvancedSenderMultiplier' field +func AdvancedSenderMultiplierFlag() string { return "advanced-sender-multiplier" } + +// GetAdvancedSenderMultiplier safely fetches the value for global configuration 'AdvancedSenderMultiplier' field +func GetAdvancedSenderMultiplier() int { return global.GetAdvancedSenderMultiplier() } + +// SetAdvancedSenderMultiplier safely sets the value for global configuration 'AdvancedSenderMultiplier' field +func SetAdvancedSenderMultiplier(v int) { global.SetAdvancedSenderMultiplier(v) } + // GetCacheGTSAccountMaxSize safely fetches the Configuration value for state's 'Cache.GTS.AccountMaxSize' field func (st *ConfigState) GetCacheGTSAccountMaxSize() (v int) { st.mutex.Lock() diff --git a/internal/transport/controller.go b/internal/transport/controller.go index 23c51f35b..f77fbbb92 100644 --- a/internal/transport/controller.go +++ b/internal/transport/controller.go @@ -57,10 +57,19 @@ type controller struct { // NewController returns an implementation of the Controller interface for creating new transports func NewController(state *state.State, federatingDB federatingdb.DB, clock pub.Clock, client httpclient.SigningClient) Controller { - applicationName := config.GetApplicationName() - host := config.GetHost() - proto := config.GetProtocol() - version := config.GetSoftwareVersion() + var ( + applicationName = config.GetApplicationName() + host = config.GetHost() + proto = config.GetProtocol() + version = config.GetSoftwareVersion() + senderMultiplier = config.GetAdvancedSenderMultiplier() + ) + + senders := senderMultiplier * runtime.GOMAXPROCS(0) + if senders < 1 { + // Clamp senders to 1. + senders = 1 + } c := &controller{ state: state, @@ -69,7 +78,7 @@ func NewController(state *state.State, federatingDB federatingdb.DB, clock pub.C client: client, trspCache: cache.New[string, *transport](0, 100, 0), userAgent: fmt.Sprintf("%s (+%s://%s) gotosocial/%s", applicationName, proto, host, version), - senders: 2 * runtime.GOMAXPROCS(0), // on batch delivery, only ever send 2*GOMAXPROCS at a time. + senders: senders, } return c diff --git a/test/envparsing.sh b/test/envparsing.sh index 2a9be5155..d1f7a9ee7 100755 --- a/test/envparsing.sh +++ b/test/envparsing.sh @@ -11,6 +11,7 @@ EXPECT=$(cat <<"EOF" "accounts-registration-open": true, "advanced-cookies-samesite": "strict", "advanced-rate-limit-requests": 6969, + "advanced-sender-multiplier": -1, "advanced-throttling-multiplier": -1, "advanced-throttling-retry-after": 10000000000, "application-name": "gts", @@ -241,6 +242,7 @@ GTS_SYSLOG_PROTOCOL='udp' \ GTS_SYSLOG_ADDRESS='127.0.0.1:6969' \ GTS_ADVANCED_COOKIES_SAMESITE='strict' \ GTS_ADVANCED_RATE_LIMIT_REQUESTS=6969 \ +GTS_ADVANCED_SENDER_MULTIPLIER=-1 \ GTS_ADVANCED_THROTTLING_MULTIPLIER=-1 \ GTS_ADVANCED_THROTTLING_RETRY_AFTER='10s' \ GTS_REQUEST_ID_HEADER='X-Trace-Id' \ diff --git a/testrig/config.go b/testrig/config.go index df9efd54f..867341607 100644 --- a/testrig/config.go +++ b/testrig/config.go @@ -120,6 +120,7 @@ func InitTestConfig() { AdvancedCookiesSamesite: "lax", AdvancedRateLimitRequests: 0, // disabled AdvancedThrottlingMultiplier: 0, // disabled + AdvancedSenderMultiplier: 0, // 1 sender only, regardless of CPU SoftwareVersion: "0.0.0-testrig",