Files
tasks/service/history/queues/action_pending_task_count.go
Alex ShtinandGitHub 8e8f1b0f7c Remove license header from every file (#7689)
## What changed?
<!-- Describe what has changed in this PR -->
Remove license header from every file. Because it is really hard to
follow in this PR here is the summary:
1. License header is removed from all `.go` and `.proto` files
:fireworks::fireworks:🎆.
2. `LICENSE` file in the root directory has only Temporal and Uber
copyrights.
3. 5 other `LICENSE` files added to the packages which have copyrights
different from Temporal and Uber: Datadog, Xargin, "Mat Ryer, Tyler
Bunnell and contributors".
4. `license_file` flag is removed from all code generation tools.
5. `copyright_file` flag is removed from `go:generate mockgen`
directive.
6. All copyright related targets are removed from `Makefile`.
7. Updated Temporal copyright year to 2025 everywhere.

## Why?
<!-- Tell your future self why have you made these changes -->
I double checked with legal department that it is not needed to have
license header in every file. One file per repo is enough. I put all
copyrights to the root `LICENSE` file and removed header from all other
files. Also updated tools and `Makefile`.
2025-05-01 18:50:21 -07:00

201 lines
5.3 KiB
Go

package queues
import (
"slices"
"time"
"go.temporal.io/server/common/collection"
)
const (
targetLoadFactor = 0.8
clearSliceThrottleDuration = 10 * time.Second
)
var _ Action = (*actionQueuePendingTask)(nil)
type (
actionQueuePendingTask struct {
attributes *AlertAttributesQueuePendingTaskCount
monitor Monitor
maxReaderCount int64
grouper Grouper
// Fields below this line make up the state of the action. Used when running the action.
// Key type is "any" to support grouping tasks by arbitrary keys.
tasksPerKey map[any]int
pendingTasksPerKeyPerSlice map[Slice]map[any]int
slicesPerKey map[any][]Slice
keysToClearPerSlice map[Slice][]any
}
)
func newQueuePendingTaskAction(
attributes *AlertAttributesQueuePendingTaskCount,
monitor Monitor,
maxReaderCount int,
grouper Grouper,
) *actionQueuePendingTask {
return &actionQueuePendingTask{
attributes: attributes,
monitor: monitor,
maxReaderCount: int64(maxReaderCount),
grouper: grouper,
}
}
func (a *actionQueuePendingTask) Name() string {
return "queue-pending-task"
}
func (a *actionQueuePendingTask) Run(readerGroup *ReaderGroup) {
// first check if the alert is still valid
if a.monitor.GetTotalPendingTaskCount() <= a.attributes.CiriticalPendingTaskCount {
return
}
// then try to shrink existing slices, which may reduce pending task count
readers := readerGroup.Readers()
if a.tryShrinkSlice(readers) {
return
}
// have to unload pending tasks to reduce pending task count
a.init()
a.gatherStatistics(readers)
a.findSliceToClear(
int(float64(a.attributes.CiriticalPendingTaskCount) * targetLoadFactor),
)
a.splitAndClearSlice(readers, readerGroup)
}
func (a *actionQueuePendingTask) tryShrinkSlice(
readers map[int64]Reader,
) bool {
for _, reader := range readers {
reader.ShrinkSlices()
}
return a.monitor.GetTotalPendingTaskCount() <= a.attributes.CiriticalPendingTaskCount
}
func (a *actionQueuePendingTask) init() {
a.tasksPerKey = make(map[any]int)
a.pendingTasksPerKeyPerSlice = make(map[Slice]map[any]int)
a.slicesPerKey = make(map[any][]Slice)
a.keysToClearPerSlice = make(map[Slice][]any)
}
func (a *actionQueuePendingTask) gatherStatistics(
readers map[int64]Reader,
) {
// gather statistic for
// 1. total # of pending tasks per key
// 2. for each slice, # of pending taks per key
// 3. for each key, a list of slices that contains pending tasks from that key,
// reversely ordered by slice range. Upon unloading, first unload newer slices.
for _, reader := range readers {
reader.WalkSlices(func(s Slice) {
pendingPerKey := s.TaskStats().PendingPerKey
a.pendingTasksPerKeyPerSlice[s] = pendingPerKey
for key, pendingTaskCount := range pendingPerKey {
a.tasksPerKey[key] += pendingTaskCount
a.slicesPerKey[key] = append(a.slicesPerKey[key], s)
}
})
}
for _, sliceList := range a.slicesPerKey {
slices.SortFunc(sliceList, func(this, that Slice) int {
thisMin := this.Scope().Range.InclusiveMin
thatMin := that.Scope().Range.InclusiveMin
// sort in largest to smallest order
return thatMin.CompareTo(thisMin)
})
}
}
func (a *actionQueuePendingTask) findSliceToClear(
targetPendingTasks int,
) {
currentPendingTasks := 0
// order key by # of pending tasks
keys := make([]any, 0, len(a.tasksPerKey))
for key, keyPendingTasks := range a.tasksPerKey {
currentPendingTasks += keyPendingTasks
keys = append(keys, key)
}
pq := collection.NewPriorityQueueWithItems(
func(this, that any) bool {
return a.tasksPerKey[this] > a.tasksPerKey[that]
},
keys,
)
for currentPendingTasks > targetPendingTasks && !pq.IsEmpty() {
key := pq.Remove()
sliceList := a.slicesPerKey[key]
if len(sliceList) == 0 {
panic("Found key with non-zero pending task count but has no correspoding Slice")
}
// pop the first slice in the list
sliceToClear := sliceList[0]
sliceList = sliceList[1:]
a.slicesPerKey[key] = sliceList
tasksCleared := a.pendingTasksPerKeyPerSlice[sliceToClear][key]
a.tasksPerKey[key] -= tasksCleared
currentPendingTasks -= tasksCleared
if a.tasksPerKey[key] > 0 {
pq.Add(key)
}
a.keysToClearPerSlice[sliceToClear] = append(a.keysToClearPerSlice[sliceToClear], key)
}
}
func (a *actionQueuePendingTask) splitAndClearSlice(
readers map[int64]Reader,
readerGroup *ReaderGroup,
) {
for readerID, reader := range readers {
if readerID == int64(a.maxReaderCount)-1 {
// we can't do further split, have to clear entire slice
cleared := false
reader.ClearSlices(func(s Slice) bool {
_, ok := a.keysToClearPerSlice[s]
cleared = cleared || ok
return ok
})
if cleared {
reader.Pause(clearSliceThrottleDuration)
}
continue
}
var splitSlices []Slice
reader.SplitSlices(func(s Slice) ([]Slice, bool) {
keys, ok := a.keysToClearPerSlice[s]
if !ok {
return nil, false
}
split, remain := s.SplitByPredicate(a.grouper.Predicate(keys))
split.Clear()
splitSlices = append(splitSlices, split)
return []Slice{remain}, true
})
if len(splitSlices) == 0 {
continue
}
nextReader := readerGroup.GetOrCreateReader(readerID + 1)
nextReader.MergeSlices(splitSlices...)
nextReader.Pause(clearSliceThrottleDuration)
}
// ShrinkSlices will be triggered as part of checkpointing process
// see queueBase.handleAlert() and queueBase.checkpoint()
}