Files
traces/modules/frontend/queue/user_queues.go
Joe ElliottandGitHub e279057bcb Frontend cleanup and perf improvement (#3996)
* clean up queues on a timer

Signed-off-by: Joe Elliott <[email protected]>

* cleanup queues

Signed-off-by: Joe Elliott <[email protected]>

* changelog

Signed-off-by: Joe Elliott <[email protected]>

* review cleanup

Signed-off-by: Joe Elliott <[email protected]>

* changelog

Signed-off-by: Joe Elliott <[email protected]>

---------

Signed-off-by: Joe Elliott <[email protected]>
2024-08-23 10:12:31 -04:00

131 lines
3.0 KiB
Go

package queue
// This struct holds user queues for pending requests. It also keeps track of connected queriers,
// and mapping between users and queriers.
type queues struct {
userQueues map[string]*userQueue
// List of all users with queues, used for iteration when searching for next queue to handle.
// Users removed from the middle are replaced with "". To avoid skipping users during iteration, we only shrink
// this list when there are ""'s at the end of it.
users []string
maxUserQueueSize int
}
type userQueue struct {
ch chan Request
// Points back to 'users' field in queues. Enables quick cleanup.
index int
}
func newUserQueues(maxUserQueueSize int) *queues {
return &queues{
userQueues: map[string]*userQueue{},
users: nil,
maxUserQueueSize: maxUserQueueSize,
}
}
func (q *queues) len() int {
return len(q.userQueues)
}
func (q *queues) deleteQueue(userID string) {
uq := q.userQueues[userID]
if uq == nil {
return
}
delete(q.userQueues, userID)
q.users[uq.index] = ""
// Shrink users list size if possible. This is safe, and no users will be skipped during iteration.
for ix := len(q.users) - 1; ix >= 0 && q.users[ix] == ""; ix-- {
q.users = q.users[:ix]
}
}
// deleteEmptyQueues removes all user queues that have no length. in addition it returns true if any
// queues were removed.
func (q *queues) deleteEmptyQueues() bool {
removedQueue := false
// look for 0 len queues and remove them
for userID, uq := range q.userQueues {
if len(uq.ch) == 0 {
removedQueue = true
q.deleteQueue(userID)
}
}
return removedQueue
}
// Returns existing or new queue for user.
func (q *queues) getOrAddQueue(userID string) chan Request {
// Empty user is not allowed, as that would break our users list ("" is used for free spot).
if userID == "" {
return nil
}
uq := q.userQueues[userID]
if uq == nil {
uq = &userQueue{
ch: make(chan Request, q.maxUserQueueSize),
index: -1,
}
q.userQueues[userID] = uq
// Add user to the list of users... find first free spot, and put it there.
for ix, u := range q.users {
if u == "" {
uq.index = ix
q.users[ix] = userID
break
}
}
// ... or add to the end.
if uq.index < 0 {
uq.index = len(q.users)
q.users = append(q.users, userID)
}
}
return uq.ch
}
// Finds next queue for the querier. To support fair scheduling between users, client is expected
// to pass last user index returned by this function as argument. Is there was no previous
// last user index, use -1.
func (q *queues) getNextQueueForQuerier(lastUserIndex int) (chan Request, string, int) {
uid := lastUserIndex
for iters := 0; iters < len(q.users); iters++ {
uid = uid + 1
// Don't use "mod len(q.users)", as that could skip users at the beginning of the list
// for example when q.users has shrunk since last call.
if uid >= len(q.users) {
uid = 0
}
u := q.users[uid]
if u == "" {
continue
}
q := q.userQueues[u]
if len(q.ch) == 0 {
continue
}
return q.ch, u, uid
}
return nil, "", uid
}