2020-02-23 11:35:47 +00:00
|
|
|
package persistentqueue
|
|
|
|
|
|
|
|
import (
|
2021-04-26 21:23:25 +00:00
|
|
|
"fmt"
|
2023-03-28 01:33:05 +00:00
|
|
|
"path/filepath"
|
2020-02-23 11:35:47 +00:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
2020-09-16 14:30:04 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
2020-02-23 11:35:47 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
2021-04-26 21:23:25 +00:00
|
|
|
"github.com/VictoriaMetrics/metrics"
|
2020-02-23 11:35:47 +00:00
|
|
|
)
|
|
|
|
|
2021-04-05 16:19:58 +00:00
|
|
|
// FastQueue is fast persistent queue, which prefers sending data via memory.
|
2020-02-23 11:35:47 +00:00
|
|
|
//
|
|
|
|
// It falls back to sending data via file when readers don't catch up with writers.
|
|
|
|
type FastQueue struct {
|
|
|
|
// my protects the state of FastQueue.
|
|
|
|
mu sync.Mutex
|
|
|
|
|
|
|
|
// cond is used for notifying blocked readers when new data has been added
|
|
|
|
// or when MustClose is called.
|
|
|
|
cond sync.Cond
|
|
|
|
|
2023-11-25 09:31:30 +00:00
|
|
|
// isPQDisabled is set to true when pq is disabled.
|
2023-11-24 12:42:11 +00:00
|
|
|
isPQDisabled bool
|
2023-11-25 09:31:30 +00:00
|
|
|
|
2020-02-28 17:57:39 +00:00
|
|
|
// pq is file-based queue
|
2021-04-05 16:19:58 +00:00
|
|
|
pq *queue
|
2020-02-23 11:35:47 +00:00
|
|
|
|
|
|
|
// ch is in-memory queue
|
|
|
|
ch chan *bytesutil.ByteBuffer
|
|
|
|
|
|
|
|
pendingInmemoryBytes uint64
|
|
|
|
|
2020-09-16 14:30:04 +00:00
|
|
|
lastInmemoryBlockReadTime uint64
|
|
|
|
|
2021-08-13 09:10:00 +00:00
|
|
|
stopDeadline uint64
|
2020-02-23 11:35:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MustOpenFastQueue opens persistent queue at the given path.
|
|
|
|
//
|
|
|
|
// It holds up to maxInmemoryBlocks in memory before falling back to file-based persistence.
|
2020-03-03 17:48:46 +00:00
|
|
|
//
|
|
|
|
// if maxPendingBytes is 0, then the queue size is unlimited.
|
|
|
|
// Otherwise its size is limited by maxPendingBytes. The oldest data is dropped when the queue
|
|
|
|
// reaches maxPendingSize.
|
2023-11-25 09:31:30 +00:00
|
|
|
// if isPQDisabled is set to true, then write requests that exceed in-memory buffer capacity are rejected.
|
2023-11-24 12:42:11 +00:00
|
|
|
// in-memory queue part can be stored on disk during gracefull shutdown.
|
|
|
|
func MustOpenFastQueue(path, name string, maxInmemoryBlocks int, maxPendingBytes int64, isPQDisabled bool) *FastQueue {
|
2021-04-05 16:19:58 +00:00
|
|
|
pq := mustOpen(path, name, maxPendingBytes)
|
2020-02-23 11:35:47 +00:00
|
|
|
fq := &FastQueue{
|
2023-11-24 12:42:11 +00:00
|
|
|
pq: pq,
|
|
|
|
isPQDisabled: isPQDisabled,
|
|
|
|
ch: make(chan *bytesutil.ByteBuffer, maxInmemoryBlocks),
|
2020-02-23 11:35:47 +00:00
|
|
|
}
|
|
|
|
fq.cond.L = &fq.mu
|
2020-09-16 18:13:27 +00:00
|
|
|
fq.lastInmemoryBlockReadTime = fasttime.UnixTimestamp()
|
2021-04-26 21:23:25 +00:00
|
|
|
_ = metrics.GetOrCreateGauge(fmt.Sprintf(`vm_persistentqueue_bytes_pending{path=%q}`, path), func() float64 {
|
|
|
|
fq.mu.Lock()
|
|
|
|
n := fq.pq.GetPendingBytes()
|
|
|
|
fq.mu.Unlock()
|
|
|
|
return float64(n)
|
|
|
|
})
|
2020-09-18 10:03:39 +00:00
|
|
|
pendingBytes := fq.GetPendingBytes()
|
|
|
|
logger.Infof("opened fast persistent queue at %q with maxInmemoryBlocks=%d, it contains %d pending bytes", path, maxInmemoryBlocks, pendingBytes)
|
2020-02-23 11:35:47 +00:00
|
|
|
return fq
|
|
|
|
}
|
|
|
|
|
2023-11-25 09:31:30 +00:00
|
|
|
// IsWriteBlocked checks if data can be pushed into fq
|
|
|
|
func (fq *FastQueue) IsWriteBlocked() bool {
|
2023-11-24 12:42:11 +00:00
|
|
|
if !fq.isPQDisabled {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
fq.mu.Lock()
|
|
|
|
defer fq.mu.Unlock()
|
|
|
|
return len(fq.ch) == cap(fq.ch) || fq.pq.GetPendingBytes() > 0
|
|
|
|
}
|
|
|
|
|
2021-02-18 22:31:07 +00:00
|
|
|
// UnblockAllReaders unblocks all the readers.
|
|
|
|
func (fq *FastQueue) UnblockAllReaders() {
|
2020-02-23 11:35:47 +00:00
|
|
|
fq.mu.Lock()
|
|
|
|
defer fq.mu.Unlock()
|
|
|
|
|
|
|
|
// Unblock blocked readers
|
2021-08-13 09:10:00 +00:00
|
|
|
// Allow for up to 5 seconds for sending Prometheus stale markers.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1526
|
|
|
|
fq.stopDeadline = fasttime.UnixTimestamp() + 5
|
2020-02-23 11:35:47 +00:00
|
|
|
fq.cond.Broadcast()
|
2021-02-18 22:31:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MustClose unblocks all the readers.
|
|
|
|
//
|
|
|
|
// It is expected no new writers during and after the call.
|
|
|
|
func (fq *FastQueue) MustClose() {
|
|
|
|
fq.UnblockAllReaders()
|
|
|
|
|
|
|
|
fq.mu.Lock()
|
|
|
|
defer fq.mu.Unlock()
|
2020-02-23 11:35:47 +00:00
|
|
|
|
2020-02-28 17:57:39 +00:00
|
|
|
// flush blocks from fq.ch to fq.pq, so they can be persisted
|
2020-02-23 11:35:47 +00:00
|
|
|
fq.flushInmemoryBlocksToFileLocked()
|
|
|
|
|
2020-02-28 17:57:39 +00:00
|
|
|
// Close fq.pq
|
|
|
|
fq.pq.MustClose()
|
2020-02-23 11:35:47 +00:00
|
|
|
|
2020-02-28 17:57:39 +00:00
|
|
|
logger.Infof("closed fast persistent queue at %q", fq.pq.dir)
|
2020-02-23 11:35:47 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 18:13:27 +00:00
|
|
|
func (fq *FastQueue) flushInmemoryBlocksToFileIfNeededLocked() {
|
2023-11-24 12:42:11 +00:00
|
|
|
if len(fq.ch) == 0 || fq.isPQDisabled {
|
2020-09-16 14:30:04 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if fasttime.UnixTimestamp() < fq.lastInmemoryBlockReadTime+5 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fq.flushInmemoryBlocksToFileLocked()
|
|
|
|
}
|
|
|
|
|
2020-02-23 11:35:47 +00:00
|
|
|
func (fq *FastQueue) flushInmemoryBlocksToFileLocked() {
|
|
|
|
// fq.mu must be locked by the caller.
|
|
|
|
for len(fq.ch) > 0 {
|
|
|
|
bb := <-fq.ch
|
2020-02-28 17:57:39 +00:00
|
|
|
fq.pq.MustWriteBlock(bb.B)
|
2020-02-23 11:35:47 +00:00
|
|
|
fq.pendingInmemoryBytes -= uint64(len(bb.B))
|
2020-09-16 14:30:04 +00:00
|
|
|
fq.lastInmemoryBlockReadTime = fasttime.UnixTimestamp()
|
2020-02-23 11:35:47 +00:00
|
|
|
blockBufPool.Put(bb)
|
|
|
|
}
|
2020-05-16 06:31:46 +00:00
|
|
|
// Unblock all the potentially blocked readers, so they could proceed with reading file-based queue.
|
|
|
|
fq.cond.Broadcast()
|
2020-02-23 11:35:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetPendingBytes returns the number of pending bytes in the fq.
|
|
|
|
func (fq *FastQueue) GetPendingBytes() uint64 {
|
|
|
|
fq.mu.Lock()
|
|
|
|
defer fq.mu.Unlock()
|
|
|
|
n := fq.pendingInmemoryBytes
|
2020-02-28 17:57:39 +00:00
|
|
|
n += fq.pq.GetPendingBytes()
|
2020-02-23 11:35:47 +00:00
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetInmemoryQueueLen returns the length of inmemory queue.
|
|
|
|
func (fq *FastQueue) GetInmemoryQueueLen() int {
|
|
|
|
fq.mu.Lock()
|
|
|
|
defer fq.mu.Unlock()
|
|
|
|
|
|
|
|
return len(fq.ch)
|
|
|
|
}
|
|
|
|
|
2023-11-25 09:31:30 +00:00
|
|
|
// MustWriteBlockIgnoreDisabledPQ unconditionally writes block to fq.
|
|
|
|
//
|
|
|
|
// This method allows perisisting in-memory blocks during graceful shutdown, even if persistence is disabled.
|
2023-11-24 12:42:11 +00:00
|
|
|
func (fq *FastQueue) MustWriteBlockIgnoreDisabledPQ(block []byte) {
|
2023-11-25 09:31:30 +00:00
|
|
|
if !fq.tryWriteBlock(block, true) {
|
|
|
|
logger.Fatalf("BUG: tryWriteBlock must always write data even if persistence is disabled")
|
2023-11-24 12:42:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-25 09:31:30 +00:00
|
|
|
// TryWriteBlock tries writing block to fq.
|
|
|
|
//
|
|
|
|
// false is returned if the block couldn't be written to fq when the in-memory queue is full
|
|
|
|
// and the persistent queue is disabled.
|
|
|
|
func (fq *FastQueue) TryWriteBlock(block []byte) bool {
|
|
|
|
return fq.tryWriteBlock(block, false)
|
2023-11-24 12:42:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// WriteBlock writes block to fq.
|
2023-11-25 09:31:30 +00:00
|
|
|
func (fq *FastQueue) tryWriteBlock(block []byte, ignoreDisabledPQ bool) bool {
|
2020-02-23 11:35:47 +00:00
|
|
|
fq.mu.Lock()
|
|
|
|
defer fq.mu.Unlock()
|
|
|
|
|
2023-11-25 09:31:30 +00:00
|
|
|
isPQWriteAllowed := !fq.isPQDisabled || ignoreDisabledPQ
|
2023-11-24 12:42:11 +00:00
|
|
|
|
2020-09-16 18:13:27 +00:00
|
|
|
fq.flushInmemoryBlocksToFileIfNeededLocked()
|
2020-02-28 17:57:39 +00:00
|
|
|
if n := fq.pq.GetPendingBytes(); n > 0 {
|
2020-02-23 11:35:47 +00:00
|
|
|
// The file-based queue isn't drained yet. This means that in-memory queue cannot be used yet.
|
|
|
|
// So put the block to file-based queue.
|
|
|
|
if len(fq.ch) > 0 {
|
|
|
|
logger.Panicf("BUG: the in-memory queue must be empty when the file-based queue is non-empty; it contains %d pending bytes", n)
|
|
|
|
}
|
2023-11-25 09:31:30 +00:00
|
|
|
if !isPQWriteAllowed {
|
|
|
|
return false
|
|
|
|
}
|
2020-02-28 17:57:39 +00:00
|
|
|
fq.pq.MustWriteBlock(block)
|
2023-11-24 12:42:11 +00:00
|
|
|
return true
|
2020-02-23 11:35:47 +00:00
|
|
|
}
|
|
|
|
if len(fq.ch) == cap(fq.ch) {
|
2023-11-25 09:31:30 +00:00
|
|
|
// There is no space left in the in-memory queue. Put the data to file-based queue.
|
|
|
|
if !isPQWriteAllowed {
|
2023-11-24 12:42:11 +00:00
|
|
|
return false
|
|
|
|
}
|
2020-02-23 11:35:47 +00:00
|
|
|
fq.flushInmemoryBlocksToFileLocked()
|
2020-02-28 17:57:39 +00:00
|
|
|
fq.pq.MustWriteBlock(block)
|
2023-11-24 12:42:11 +00:00
|
|
|
return true
|
2020-02-23 11:35:47 +00:00
|
|
|
}
|
2023-11-25 09:31:30 +00:00
|
|
|
// Fast path - put the block to in-memory queue.
|
2020-02-23 11:35:47 +00:00
|
|
|
bb := blockBufPool.Get()
|
|
|
|
bb.B = append(bb.B[:0], block...)
|
|
|
|
fq.ch <- bb
|
|
|
|
fq.pendingInmemoryBytes += uint64(len(block))
|
2020-05-16 06:31:46 +00:00
|
|
|
|
|
|
|
// Notify potentially blocked reader.
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/pull/484 for the context.
|
|
|
|
fq.cond.Signal()
|
2023-11-24 12:42:11 +00:00
|
|
|
return true
|
2020-02-23 11:35:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MustReadBlock reads the next block from fq to dst and returns it.
|
|
|
|
func (fq *FastQueue) MustReadBlock(dst []byte) ([]byte, bool) {
|
|
|
|
fq.mu.Lock()
|
|
|
|
defer fq.mu.Unlock()
|
|
|
|
|
|
|
|
for {
|
2021-08-13 09:10:00 +00:00
|
|
|
if fq.stopDeadline > 0 && fasttime.UnixTimestamp() > fq.stopDeadline {
|
2020-02-23 11:35:47 +00:00
|
|
|
return dst, false
|
|
|
|
}
|
|
|
|
if len(fq.ch) > 0 {
|
2020-02-28 17:57:39 +00:00
|
|
|
if n := fq.pq.GetPendingBytes(); n > 0 {
|
2020-05-16 06:31:46 +00:00
|
|
|
logger.Panicf("BUG: the file-based queue must be empty when the inmemory queue is non-empty; it contains %d pending bytes", n)
|
2020-02-23 11:35:47 +00:00
|
|
|
}
|
|
|
|
bb := <-fq.ch
|
|
|
|
fq.pendingInmemoryBytes -= uint64(len(bb.B))
|
2020-09-16 14:30:04 +00:00
|
|
|
fq.lastInmemoryBlockReadTime = fasttime.UnixTimestamp()
|
2020-02-23 11:35:47 +00:00
|
|
|
dst = append(dst, bb.B...)
|
|
|
|
blockBufPool.Put(bb)
|
|
|
|
return dst, true
|
|
|
|
}
|
2020-02-28 17:57:39 +00:00
|
|
|
if n := fq.pq.GetPendingBytes(); n > 0 {
|
2021-04-05 16:19:58 +00:00
|
|
|
data, ok := fq.pq.MustReadBlockNonblocking(dst)
|
|
|
|
if ok {
|
|
|
|
return data, true
|
|
|
|
}
|
|
|
|
dst = data
|
|
|
|
continue
|
2020-02-23 11:35:47 +00:00
|
|
|
}
|
2021-08-13 09:10:00 +00:00
|
|
|
if fq.stopDeadline > 0 {
|
|
|
|
return dst, false
|
|
|
|
}
|
2020-02-23 11:35:47 +00:00
|
|
|
// There are no blocks. Wait for new block.
|
2020-02-28 17:57:39 +00:00
|
|
|
fq.pq.ResetIfEmpty()
|
2020-02-23 11:35:47 +00:00
|
|
|
fq.cond.Wait()
|
|
|
|
}
|
|
|
|
}
|
2023-03-28 01:15:28 +00:00
|
|
|
|
2023-03-28 03:05:38 +00:00
|
|
|
// Dirname returns the directory name for persistent queue.
|
2023-03-28 01:33:05 +00:00
|
|
|
func (fq *FastQueue) Dirname() string {
|
|
|
|
return filepath.Base(fq.pq.dir)
|
2023-03-28 01:15:28 +00:00
|
|
|
}
|