-
Notifications
You must be signed in to change notification settings - Fork 86
feat: add NATS JetStream workqueue pattern support #767
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: add NATS JetStream workqueue pattern support #767
Conversation
- Add UseExistingStream bool to config struct - Add Retention string to createStreamConfig struct - Prepare for workqueue pattern support
- Add isValidRetentionPolicy validation function - Add retentionPolicy conversion function - Validate mutual exclusivity of use-existing-stream and create-stream - Validate retention-policy values (limits or workqueue) - Set retention-policy default to 'limits' for backward compatibility"
- Add verifyExistingStream to query and log stream info - Return error if stream doesn't exist when use-existing-stream is true - Log all relevant stream configuration details"
- Add specific check for nats.ErrStreamNotFound - Remove unreachable stream == nil check - Improve error message clarity for stream not found case"
- Update createStream to check use-existing-stream mode first - Add Retention field to StreamConfig with retentionPolicy conversion - Support both workqueue and limits retention policies - Maintain backward compatibility with existing behavior
- Add consumerMode type with single and multi constants - Add ConsumerMode field to config struct - Add FilterSubjects field to config struct - Prepare for workqueue consumer pattern support
- Set consumer-mode default to 'single' - Validate consumer-mode values (single or multi) - Require filter-subjects when consumer-mode is multi - Ensure backward compatibility with default single mode
- Add filter subject determination based on consumer mode - Single mode: use configured subjects as filter - Multi mode: use explicitly configured filter-subjects - Apply filter subjects to ConsumerConfig.FilterSubjects - Support workqueue pattern with proper subject filtering
- Add output tests for retention policy validation and conversion - Add output tests for setDefaults validation logic - Add input tests for consumer mode validation - Add input tests for filter-subjects requirements - All 47 tests passing (26 output + 21 input)
- Document use-existing-stream and retention-policy for output - Document consumer-mode and filter-subjects for input - Add JetStream Queue Patterns section to output docs - Add JetStream Consumer Modes section to input docs - Include practical examples and decision guides
- Query stream info to determine retention policy - Use AckExplicitPolicy for workqueue streams (required by NATS) - Use AckAllPolicy for limits-based streams (backward compatible) - Fixes 400 error: 'workqueue stream requires explicit ack'
- Query stream info to determine retention policy - Workqueue streams require AckExplicitPolicy (explicit ack) - Workqueue streams require DeliverAllPolicy (deliver all) - Limits-based streams use configured policies (backward compatible) - Add tests for toJSDeliverPolicy conversion function - Fixes NATS errors: 'workqueue stream requires explicit ack' and 'consumer must be deliver all'
- Use fmt.Sprintf with loggingPrefix format string correctly - Fixes log output showing literal '%s' instead of worker name
| } | ||
|
|
||
| declare -a modules=("." "pkg/api" "pkg/cache") | ||
| declare -a modules=("." "pkg/api" "pkg/cache", "pkg/inputs/jetstream_input", "pkg/outputs/nats_outputs/jetstream") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is needed, the inputs and outputs should be included in module .
| Subject string `mapstructure:"subject,omitempty" json:"subject,omitempty"` | ||
| SubjectFormat subjectFormat `mapstructure:"subject-format,omitempty" json:"subject-format,omitempty"` | ||
| CreateStream *createStreamConfig `mapstructure:"create-stream,omitempty" json:"create-stream,omitempty"` | ||
| UseExistingStream bool `mapstructure:"use-existing-stream,omitempty" json:"use-existing-stream,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure we need a separate knob for this. We can replace it with CreateStream == nil
| Stream string `mapstructure:"stream,omitempty"` | ||
| Subjects []string `mapstructure:"subjects,omitempty"` | ||
| SubjectFormat subjectFormat `mapstructure:"subject-format,omitempty" json:"subject-format,omitempty"` | ||
| ConsumerMode consumerMode `mapstructure:"consumer-mode,omitempty" json:"consumer-mode,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@protonjhow I think we don’t actually need consumerMode or a separate filterSubjects field here because this input always creates a single durable consumer and runs multiple workers on that same consumer. The case of multiple (different) consumers happens when running multiple gNMIc instances, a single instance config does not need to be aware of other instances configuration.
| // Get stream info to determine retention policy | ||
| streamInfo, err := s.Info(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get stream info: %v", err) | ||
| } | ||
|
|
||
| // Determine ack policy and deliver policy based on stream retention | ||
| // Workqueue streams have specific requirements | ||
| ackPolicy := jetstream.AckAllPolicy | ||
| deliverPolicy := toJSDeliverPolicy(n.Cfg.DeliverPolicy) | ||
|
|
||
| if streamInfo.Config.Retention == jetstream.WorkQueuePolicy { | ||
| // Workqueue streams require explicit ack | ||
| ackPolicy = jetstream.AckExplicitPolicy | ||
| // Workqueue streams require deliver all policy | ||
| deliverPolicy = jetstream.DeliverAllPolicy | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the bit that solves your use case (worker-queue retention).
I would change a small bit: if the retention policy is WorkQueue we should allow deliverPolicy = deliverNewPolicy as well as DeliverAllPolicy. It's not your use case, but it allows users to say: "pick up only new jobs"
| // Determine filter subjects based on consumer mode | ||
| var filterSubjects []string | ||
| switch n.Cfg.ConsumerMode { | ||
| case consumerModeSingle: | ||
| // Use configured subjects as filter | ||
| filterSubjects = n.Cfg.Subjects | ||
| case consumerModeMulti: | ||
| // Use explicitly configured filter-subjects | ||
| filterSubjects = n.Cfg.FilterSubjects | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When ConsumerMode and FilterSubjects are removed we should just use Subjects in CreateOrUpdateConsumer
In reference to #749, here is a PR for review that adds NATS Jetstream WorkQueue support.
Before anyone asks, yes, claude was involved, but with significant care.
Summary
Adds support for NATS JetStream workqueue retention pattern to gnmic's JetStream input and output implementations, enabling exactly-once message processing for task distribution scenarios.
Output Changes
use-existing-streamconfiguration option to use pre-existing streamsretention-policyconfiguration option with support forlimits(default) andworkqueueretention policiesInput Changes
consumer-modeconfiguration option withsingle(default) andmultimodesfilter-subjectsconfiguration for multi-consumer workqueue scenariosTesting
Documentation
Test Plan
Backward Compatibility
All changes are fully backward compatible:
limits(existing behavior)single(existing behavior)Implementation Details
The implementation automatically detects stream retention policies and adapts consumer behavior accordingly: