mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
lib/timerpool: use timer pool in concurrency limiters
This should reduce the number of memory allocations in highly loaded system
This commit is contained in:
parent
0eae39daa7
commit
e307a4d92c
4 changed files with 51 additions and 6 deletions
|
@ -4,6 +4,8 @@ import (
|
|||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -21,14 +23,15 @@ var (
|
|||
func Do(f func() error) error {
|
||||
// Limit the number of conurrent inserts in order to prevent from excess
|
||||
// memory usage and CPU trashing.
|
||||
t := time.NewTimer(waitDuration)
|
||||
t := timerpool.Get(waitDuration)
|
||||
select {
|
||||
case ch <- struct{}{}:
|
||||
t.Stop()
|
||||
timerpool.Put(t)
|
||||
err := f()
|
||||
<-ch
|
||||
return err
|
||||
case <-t.C:
|
||||
timerpool.Put(t)
|
||||
return fmt.Errorf("the server is overloaded with %d concurrent inserts; either increase the number of CPUs or reduce the load", cap(ch))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
|
@ -43,12 +44,13 @@ func Stop() {
|
|||
func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
// Limit the number of concurrent queries.
|
||||
// Sleep for a while until giving up. This should resolve short bursts in requests.
|
||||
t := time.NewTimer(*maxQueueDuration)
|
||||
t := timerpool.Get(*maxQueueDuration)
|
||||
select {
|
||||
case concurrencyCh <- struct{}{}:
|
||||
t.Stop()
|
||||
timerpool.Put(t)
|
||||
defer func() { <-concurrencyCh }()
|
||||
case <-t.C:
|
||||
timerpool.Put(t)
|
||||
httpserver.Errorf(w, "cannot handle more than %d concurrent requests", cap(concurrencyCh))
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -553,12 +554,13 @@ func (s *Storage) AddRows(mrs []MetricRow, precisionBits uint8) error {
|
|||
// Limit the number of concurrent goroutines that may add rows to the storage.
|
||||
// This should prevent from out of memory errors and CPU trashing when too many
|
||||
// goroutines call AddRows.
|
||||
t := time.NewTimer(addRowsTimeout)
|
||||
t := timerpool.Get(addRowsTimeout)
|
||||
select {
|
||||
case addRowsConcurrencyCh <- struct{}{}:
|
||||
t.Stop()
|
||||
timerpool.Put(t)
|
||||
defer func() { <-addRowsConcurrencyCh }()
|
||||
case <-t.C:
|
||||
timerpool.Put(t)
|
||||
return fmt.Errorf("Cannot add %d rows to storage in %s, since it is overloaded with %d concurrent writers. Add more CPUs or reduce load",
|
||||
len(mrs), addRowsTimeout, cap(addRowsConcurrencyCh))
|
||||
}
|
||||
|
|
38
lib/timerpool/timerpool.go
Normal file
38
lib/timerpool/timerpool.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package timerpool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// Get returns a timer for the given duration d from the pool.
|
||||
//
|
||||
// Return back the timer to the pool with Put.
|
||||
func Get(d time.Duration) *time.Timer {
|
||||
if v := timerPool.Get(); v != nil {
|
||||
t := v.(*time.Timer)
|
||||
if t.Reset(d) {
|
||||
logger.Panicf("BUG: active timer trapped to the pool!")
|
||||
}
|
||||
return t
|
||||
}
|
||||
return time.NewTimer(d)
|
||||
}
|
||||
|
||||
// Put returns t to the pool.
|
||||
//
|
||||
// t cannot be accessed after returning to the pool.
|
||||
func Put(t *time.Timer) {
|
||||
if !t.Stop() {
|
||||
// Drain t.C if it wasn't obtained by the caller yet.
|
||||
select {
|
||||
case <-t.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
timerPool.Put(t)
|
||||
}
|
||||
|
||||
var timerPool sync.Pool
|
Loading…
Reference in a new issue