-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
nsqd: channel sampling #223
Changes from all commits
ba160ed
6a18428
30e9eca
d79afe8
6f4dfa9
271e077
a75d82e
9d0066d
9fe4b6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,3 +41,6 @@ _testmain.go | |
*.exe | ||
|
||
profile | ||
|
||
# Vim files | ||
*.sw[op] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ import ( | |
"github.com/bitly/nsq/util/pqueue" | ||
"log" | ||
"math" | ||
"math/rand" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
"sync/atomic" | ||
|
@@ -44,7 +46,8 @@ type Channel struct { | |
notifier Notifier | ||
options *nsqdOptions | ||
|
||
backend BackendQueue | ||
backend BackendQueue | ||
properties channelProperties | ||
|
||
incomingMsgChan chan *nsq.Message | ||
memoryMsgChan chan *nsq.Message | ||
|
@@ -54,11 +57,10 @@ type Channel struct { | |
exitFlag int32 | ||
|
||
// state tracking | ||
clients []Consumer | ||
paused int32 | ||
ephemeralChannel bool | ||
deleteCallback func(*Channel) | ||
deleter sync.Once | ||
clients []Consumer | ||
paused int32 | ||
deleteCallback func(*Channel) | ||
deleter sync.Once | ||
|
||
// TODO: these can be DRYd up | ||
deferredMessages map[nsq.MessageID]*pqueue.Item | ||
|
@@ -81,6 +83,12 @@ type inFlightMessage struct { | |
ts time.Time | ||
} | ||
|
||
// Channel property struct | ||
type channelProperties struct { | ||
ephemeralChannel bool | ||
sampleRate int32 | ||
} | ||
|
||
// NewChannel creates a new instance of the Channel type and returns a pointer | ||
func NewChannel(topicName string, channelName string, options *nsqdOptions, | ||
notifier Notifier, deleteCallback func(*Channel)) *Channel { | ||
|
@@ -101,8 +109,14 @@ func NewChannel(topicName string, channelName string, options *nsqdOptions, | |
|
||
c.initPQ() | ||
|
||
if strings.HasSuffix(channelName, "#ephemeral") { | ||
c.ephemeralChannel = true | ||
// Split the channel name into properties if any are passed | ||
channelProperties := strings.SplitN(channelName, "#", 2) | ||
if len(channelProperties) > 1 { | ||
c.setChannelProperties(channelProperties[1]) | ||
} | ||
|
||
// Create the channels | ||
if c.properties.ephemeralChannel == true { | ||
c.backend = NewDummyBackendQueue() | ||
} else { | ||
c.backend = NewDiskQueue(backendName, options.dataPath, options.maxBytesPerFile, | ||
|
@@ -120,6 +134,52 @@ func NewChannel(topicName string, channelName string, options *nsqdOptions, | |
return c | ||
} | ||
|
||
// Set the channel properties for the channel by parsing the channel name string properties | ||
func (c *Channel) setChannelProperties(properties string) { | ||
channelProperties := strings.Split(properties, ";") | ||
// Iterate over all properties and set the valid ones on the channel | ||
for _, property := range channelProperties { | ||
var propertyField = "" | ||
var propertyValue = "" | ||
if strings.Contains(property, "=") { | ||
p := strings.Split(property, "=") | ||
propertyField = p[0] | ||
propertyValue = p[1] | ||
} else { | ||
propertyField = property | ||
propertyValue = "true" | ||
} | ||
switch strings.ToLower(propertyField) { | ||
case "ephemeral": | ||
if propertyValue == "true" { | ||
c.properties.ephemeralChannel = true | ||
} else { | ||
c.properties.ephemeralChannel = false | ||
} | ||
case "samplerate": | ||
c.properties.sampleRate = c.validateSampleRate(propertyValue) | ||
} | ||
} | ||
} | ||
|
||
// Need to validate the sampleRate passed in on channel instantiation | ||
func (c *Channel) validateSampleRate(dirtySampleRate string) int32 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. two things
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is making the assumption that everyone will want to sample at a resolution of 1%. What about the possibility of someone wanting to sample at 33.33%? This makes send when people are doing billions of messages, as that extra decimal can make the difference of hundreds of thousands of messages. I would lean toward only allowing floats and not allowing the 0-100. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As it was implemented before it was the same resolution (it converted to int on the way out). I personally don't care that much... a percentage is a percentage is a percentage... losing fractions of a percent granularity is meh IMO. |
||
sampleRate, err := strconv.ParseInt(dirtySampleRate, 10, 0) | ||
// If we get an error when trying to ParseInt, fail on channel creation | ||
if err != nil { | ||
log.Printf("Float conversion error on %s: Setting sample rate to 0", dirtySampleRate) | ||
} | ||
|
||
// If 1<rate<100, consider it a number and use that (100 means no sampling) | ||
if (int32(sampleRate) >= int32(1)) && (int32(sampleRate) <= int32(100)) { | ||
return int32(sampleRate) | ||
} else { | ||
// Here we fail on channel creation because the sampleRate makes no sense | ||
} | ||
|
||
return int32(0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of adding this extra |
||
} | ||
|
||
func (c *Channel) initPQ() { | ||
pqSize := int(math.Max(1, float64(c.options.memQueueSize)/10)) | ||
|
||
|
@@ -392,7 +452,7 @@ func (c *Channel) RemoveClient(client Consumer) { | |
c.clients = finalClients | ||
} | ||
|
||
if len(c.clients) == 0 && c.ephemeralChannel == true { | ||
if len(c.clients) == 0 && c.properties.ephemeralChannel == true { | ||
go c.deleter.Do(func() { c.deleteCallback(c) }) | ||
} | ||
} | ||
|
@@ -572,6 +632,13 @@ func (c *Channel) messagePump() { | |
goto exit | ||
} | ||
|
||
// If we are sampling, discard the sampled messages here | ||
if c.properties.sampleRate > 0 { | ||
if rand.Int31n(100) > c.properties.sampleRate { | ||
continue | ||
} | ||
} | ||
|
||
msg.Attempts++ | ||
|
||
atomic.StoreInt32(&c.bufferedCount, 1) | ||
|
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.
this section probably deserves to be its own function