Skip to content

Commit 58f07b5

Browse files
authored
queue-specific fetch interval and cooldown configs (#994)
1 parent 19ce499 commit 58f07b5

3 files changed

Lines changed: 75 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Added `JobDeleteMany` operations that remove many jobs in a single operation according to input criteria. [PR #962](https://github.com/riverqueue/river/pull/962)
1818
- Added `Client.Schema()` method to return a client's configured schema. [PR #983](https://github.com/riverqueue/river/pull/983).
1919
- Integrated riverui queries into the driver system to pave the way for multi-driver UI support. [PR #983](https://github.com/riverqueue/river/pull/983).
20+
- Added `QueueConfig` level `FetchCooldown` and `FetchPollInterval` settings to enable queue-specific job fetch intervals. For example, a queue of high-priority jobs could be checked more often to improve responsiveness, while one with slow or time-insensitive tasks could be checked infrequently to reduce database load. [PR #994](https://github.com/riverqueue/river/pull/994).
2021

2122
### Changed
2223

client.go

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,17 @@ type Config struct {
130130
//
131131
// Throughput is limited by this value.
132132
//
133+
// Individual QueueConfig structs may override this for a specific queue.
134+
//
133135
// Defaults to 100 ms.
134136
FetchCooldown time.Duration
135137

136138
// FetchPollInterval is the amount of time between periodic fetches for new
137139
// jobs. Typically new jobs will be picked up ~immediately after insert via
138140
// LISTEN/NOTIFY, but this provides a fallback.
139141
//
142+
// Individual QueueConfig structs may override this for a specific queue.
143+
//
140144
// Defaults to 1 second.
141145
FetchPollInterval time.Duration
142146

@@ -484,7 +488,7 @@ func (c *Config) validate() error {
484488
}
485489

486490
for queue, queueConfig := range c.Queues {
487-
if err := queueConfig.validate(queue); err != nil {
491+
if err := queueConfig.validate(queue, c.FetchCooldown, c.FetchPollInterval); err != nil {
488492
return err
489493
}
490494
}
@@ -521,6 +525,25 @@ func (c *Config) willExecuteJobs() bool {
521525

522526
// QueueConfig contains queue-specific configuration.
523527
type QueueConfig struct {
528+
// FetchCooldown is the minimum amount of time to wait between fetches of new
529+
// jobs. Jobs will only be fetched *at most* this often, but if no new jobs
530+
// are coming in via LISTEN/NOTIFY then fetches may be delayed as long as
531+
// FetchPollInterval.
532+
//
533+
// Throughput is limited by this value.
534+
//
535+
// If non-zero, this overrides the FetchCooldown setting in the Client's
536+
// Config.
537+
FetchCooldown time.Duration
538+
539+
// FetchPollInterval is the amount of time between periodic fetches for new
540+
// jobs. Typically new jobs will be picked up ~immediately after insert via
541+
// LISTEN/NOTIFY, but this provides a fallback.
542+
//
543+
// If non-zero, this overrides the FetchCooldown setting in the Client's
544+
// Config.
545+
FetchPollInterval time.Duration
546+
524547
// MaxWorkers is the maximum number of workers to run for the queue, or put
525548
// otherwise, the maximum parallelism to run.
526549
//
@@ -534,7 +557,20 @@ type QueueConfig struct {
534557
MaxWorkers int
535558
}
536559

537-
func (c QueueConfig) validate(queueName string) error {
560+
func (c QueueConfig) validate(queueName string, clientFetchCooldown time.Duration, clientFetchPollInterval time.Duration) error {
561+
if c.FetchCooldown < 0 {
562+
return fmt.Errorf("FetchCooldown cannot be less than zero")
563+
}
564+
if c.FetchPollInterval < 0 {
565+
return fmt.Errorf("FetchPollInterval cannot be less than zero")
566+
}
567+
568+
resolvedFetchCooldown := cmp.Or(c.FetchCooldown, clientFetchCooldown)
569+
resolvedFetchPollInterval := cmp.Or(c.FetchPollInterval, clientFetchPollInterval)
570+
if resolvedFetchPollInterval < resolvedFetchCooldown {
571+
return fmt.Errorf("FetchPollInterval cannot be less than FetchCooldown")
572+
}
573+
538574
if c.MaxWorkers < 1 || c.MaxWorkers > QueueNumWorkersMax {
539575
return fmt.Errorf("invalid number of workers for queue %q: %d", queueName, c.MaxWorkers)
540576
}
@@ -691,8 +727,10 @@ func NewClient[TTx any](driver riverdriver.Driver[TTx], config *Config) (*Client
691727
}
692728

693729
client.queues = &QueueBundle{
694-
addProducer: client.addProducer,
695-
clientWillExecuteJobs: config.willExecuteJobs(),
730+
addProducer: client.addProducer,
731+
clientFetchCooldown: config.FetchCooldown,
732+
clientFetchPollInterval: config.FetchPollInterval,
733+
clientWillExecuteJobs: config.willExecuteJobs(),
696734
}
697735

698736
baseservice.Init(archetype, &client.baseService)
@@ -2022,8 +2060,8 @@ func (c *Client[TTx]) addProducer(queueName string, queueConfig QueueConfig) (*p
20222060
ClientID: c.config.ID,
20232061
Completer: c.completer,
20242062
ErrorHandler: c.config.ErrorHandler,
2025-
FetchCooldown: c.config.FetchCooldown,
2026-
FetchPollInterval: c.config.FetchPollInterval,
2063+
FetchCooldown: cmp.Or(queueConfig.FetchCooldown, c.config.FetchCooldown),
2064+
FetchPollInterval: cmp.Or(queueConfig.FetchPollInterval, c.config.FetchPollInterval),
20272065
HookLookupByJob: c.hookLookupByJob,
20282066
HookLookupGlobal: c.hookLookupGlobal,
20292067
JobTimeout: c.config.JobTimeout,
@@ -2582,6 +2620,9 @@ type QueueBundle struct {
25822620
// Function that adds a producer to the associated client.
25832621
addProducer func(queueName string, queueConfig QueueConfig) (*producer, error)
25842622

2623+
clientFetchCooldown time.Duration
2624+
clientFetchPollInterval time.Duration
2625+
25852626
clientWillExecuteJobs bool
25862627

25872628
fetchCtx context.Context //nolint:containedctx
@@ -2602,7 +2643,7 @@ func (b *QueueBundle) Add(queueName string, queueConfig QueueConfig) error {
26022643
return errors.New("client is not configured to execute jobs, cannot add queue")
26032644
}
26042645

2605-
if err := queueConfig.validate(queueName); err != nil {
2646+
if err := queueConfig.validate(queueName, b.clientFetchCooldown, b.clientFetchPollInterval); err != nil {
26062647
return err
26072648
}
26082649

client_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7183,6 +7183,32 @@ func Test_NewClient_Validations(t *testing.T) {
71837183
name: "Queues can be empty",
71847184
configFunc: func(config *Config) { config.Queues = make(map[string]QueueConfig) },
71857185
},
7186+
{
7187+
name: "Queues FetchCooldown can be overridden",
7188+
configFunc: func(config *Config) {
7189+
config.Queues = map[string]QueueConfig{QueueDefault: {FetchCooldown: 9 * time.Millisecond, MaxWorkers: 1}}
7190+
},
7191+
validateResult: func(t *testing.T, client *Client[pgx.Tx]) { //nolint:thelper
7192+
require.Equal(t, 9*time.Millisecond, client.producersByQueueName[QueueDefault].config.FetchCooldown)
7193+
},
7194+
},
7195+
{
7196+
name: "Queues FetchCooldown can't be greater than Client FetchPollInterval",
7197+
configFunc: func(config *Config) {
7198+
config.Queues = map[string]QueueConfig{QueueDefault: {FetchCooldown: 10 * time.Millisecond, MaxWorkers: 1}}
7199+
config.FetchPollInterval = 9 * time.Millisecond
7200+
},
7201+
wantErr: fmt.Errorf("FetchPollInterval cannot be shorter than FetchCooldown (%s)", FetchCooldownDefault),
7202+
},
7203+
{
7204+
name: "Queues FetchPollInterval can be overridden",
7205+
configFunc: func(config *Config) {
7206+
config.Queues = map[string]QueueConfig{QueueDefault: {FetchPollInterval: 9 * time.Second, MaxWorkers: 1}}
7207+
},
7208+
validateResult: func(t *testing.T, client *Client[pgx.Tx]) { //nolint:thelper
7209+
require.Equal(t, 9*time.Second, client.producersByQueueName[QueueDefault].config.FetchPollInterval)
7210+
},
7211+
},
71867212
{
71877213
name: "Queues MaxWorkers can't be negative",
71887214
configFunc: func(config *Config) {

0 commit comments

Comments
 (0)