Skip to content

Conversation

@radykal-com
Copy link
Contributor

Summary

Avoid using the mutex in each metric name parse by passing each worker goroutine its own graphite.Parser.

Checklist

  • No AI generated code was used in this PR

Related issues

resolves #12313

Benchmark

There is a big difference in the overall throughput when using multiple threads

Old code

BenchmarkParser-10                               	  305120	      3939 ns/op
BenchmarkUDP-10                                  	       1	2014095375 ns/op
BenchmarkUDPThreads4-10                          	     128	   8014808 ns/op
BenchmarkUDPThreads8-10                          	     126	   7963413 ns/op
BenchmarkUDPThreads16-10                         	     123	   8457920 ns/op
BenchmarkTCP-10                                  	       1	1583591084 ns/op
BenchmarkParse-10                                	   29188	     41003 ns/op
BenchmarkParseWithTemplate-10                    	   32236	     36990 ns/op
BenchmarkParseWithTemplateAndFilter-10           	   31557	     37952 ns/op
BenchmarkParseWith2TemplatesAndFilter-10         	   31798	     37983 ns/op
BenchmarkParseWith2Templates3TagsAndFilter-10    	   25666	     46949 ns/op
PASS

New code

BenchmarkParser-10                               	  324426	      3748 ns/op
BenchmarkUDP-10                                  	       1	1545554250 ns/op
BenchmarkUDPThreads4-10                          	     235	   4815471 ns/op
BenchmarkUDPThreads8-10                          	     247	   4646433 ns/op
BenchmarkUDPThreads16-10                         	     220	   4568423 ns/op
BenchmarkTCP-10                                  	       1	1590451042 ns/op
BenchmarkParse-10                                	   31209	     38400 ns/op
BenchmarkParseWithTemplate-10                    	   31294	     38442 ns/op
BenchmarkParseWithTemplateAndFilter-10           	   30872	     38969 ns/op
BenchmarkParseWith2TemplatesAndFilter-10         	   30714	     39148 ns/op
BenchmarkParseWith2Templates3TagsAndFilter-10    	   22514	     53415 ns/op
PASS

@telegraf-tiger
Copy link
Contributor

Thanks so much for the pull request!
🤝 ✒️ Just a reminder that the CLA has not yet been signed, and we'll need it before merging. Please sign the CLA when you get a chance, then post a comment here saying !signed-cla

@radykal-com radykal-com changed the title Fix issue 12313 bad statsD input performance feat(inputs.statsd): Improve performance Oct 22, 2025
@telegraf-tiger telegraf-tiger bot added area/statsd feat Improvement on an existing feature such as adding a new setting/mode to an existing plugin plugin/input 1. Request for new input plugins 2. Issues/PRs that are related to input plugins labels Oct 22, 2025
@radykal-com
Copy link
Contributor Author

!signed-cla

Comment on lines 138 to 139
err := parser.Init()
require.NoError(b, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for all other occurrences

Suggested change
err := parser.Init()
require.NoError(b, err)
require.NoError(b, parser.Init())

Comment on lines 570 to 574
p := &graphite.Parser{Separator: s.MetricSeparator, Templates: s.Templates}
err := p.Init()
if err != nil {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe extract in a new func which returns *graphite.Parser, error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created it and used it also in tests

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, that was the idea.


// newGraphiteParser initializes and returns a new graphite.Parser. graphite.Parser returned is not safe to be used in
// multiple goroutines.
func newGraphiteParser(s *Statsd) (*graphite.Parser, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this?

Suggested change
func newGraphiteParser(s *Statsd) (*graphite.Parser, error) {
func (s *Statsd) newGraphiteParser() (*graphite.Parser, error) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hipska one of the pipelines failed but I think it's not related to the changes

@telegraf-tiger
Copy link
Contributor

Copy link
Member

@srebhan srebhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution @radykal-com!

I think your code is racy for the case where another message is coming in via s.in in the parser function while we are currently using the parser. More specifically when setting p.DefaultTags in parseName().
If a second call (for another s.in message) arrives at that line before (or even worse while) the first call is executing the templates you will get mangled tags in your template result. We had this issue before and therefore introduced the lock.

What you could do though is to use a sync.Pool or parsers to be able to handle multiple messages in parallel while reusing the parser!

@srebhan srebhan self-assigned this Oct 27, 2025
@radykal-com
Copy link
Contributor Author

radykal-com commented Oct 27, 2025

Thanks for your contribution @radykal-com!

I think your code is racy for the case where another message is coming in via s.in in the parser function while we are currently using the parser. More specifically when setting p.DefaultTags in parseName(). If a second call (for another s.in message) arrives at that line before (or even worse while) the first call is executing the templates you will get mangled tags in your template result. We had this issue before and therefore introduced the lock.

What you could do though is to use a sync.Pool or parsers to be able to handle multiple messages in parallel while reusing the parser!

That cannot happen because each parser call is in it's own goroutine (each goroutine with its own parser struct) and no further goroutines are created inside. What I mean is that every message received from s.in is executed in a single goroutine, so a second read cannot happen until the previous one is completely processed. What is running in parallel is that there are N goroutines running the parser() function, so there are N receiving from the s.in that's way there is a parser per goroutine created before the read loop in the parser method.

I'm pretty sure its working because the code is running in our high traffic metrics aggregator for the past week and is working fine (no panic) and the tests just pass OK with the -race flag

@radykal-com radykal-com requested a review from srebhan October 27, 2025 11:41
@radykal-com
Copy link
Contributor Author

@Hipska any chance to rerun the pipeline to get the failing one pass?

Copy link
Member

@srebhan srebhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your explanation @radykal-com! I agree with your assessment.

@srebhan srebhan added the ready for final review This pull request has been reviewed and/or tested by multiple users and is ready for a final review. label Oct 29, 2025
@srebhan srebhan assigned skartikey and mstrandboge and unassigned srebhan Oct 29, 2025
Copy link
Contributor

@skartikey skartikey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@radykal-com Nice work! Thanks for the contribution!

@skartikey skartikey merged commit 6cc1353 into influxdata:master Oct 30, 2025
26 of 27 checks passed
@github-actions github-actions bot added this to the v1.37.0 milestone Oct 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/statsd feat Improvement on an existing feature such as adding a new setting/mode to an existing plugin plugin/input 1. Request for new input plugins 2. Issues/PRs that are related to input plugins ready for final review This pull request has been reviewed and/or tested by multiple users and is ready for a final review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

inputs.statsd - Increasing number of UDP pending messages dropped

5 participants