2020-02-23 12:35:47 +01:00
package persistentqueue
import (
2021-04-26 23:23:25 +02:00
"fmt"
2023-03-28 03:33:05 +02:00
"path/filepath"
2020-02-23 12:35:47 +01:00
"sync"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
2020-09-16 16:30:04 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
2020-02-23 12:35:47 +01:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
2021-04-26 23:23:25 +02:00
"github.com/VictoriaMetrics/metrics"
2020-02-23 12:35:47 +01:00
)
2021-04-05 18:19:58 +02:00
// FastQueue is fast persistent queue, which prefers sending data via memory.
2020-02-23 12:35:47 +01:00
//
// It falls back to sending data via file when readers don't catch up with writers.
type FastQueue struct {
2024-06-20 11:30:36 +02:00
// mu protects the state of FastQueue.
2020-02-23 12:35:47 +01:00
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 10:31:30 +01:00
// isPQDisabled is set to true when pq is disabled.
2023-11-24 13:42:11 +01:00
isPQDisabled bool
2023-11-25 10:31:30 +01:00
2020-02-28 18:57:39 +01:00
// pq is file-based queue
2021-04-05 18:19:58 +02:00
pq * queue
2020-02-23 12:35:47 +01:00
// ch is in-memory queue
ch chan * bytesutil . ByteBuffer
pendingInmemoryBytes uint64
2020-09-16 16:30:04 +02:00
lastInmemoryBlockReadTime uint64
2021-08-13 11:10:00 +02:00
stopDeadline uint64
2020-02-23 12:35:47 +01: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 18:48:46 +01: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 10:31:30 +01:00
// if isPQDisabled is set to true, then write requests that exceed in-memory buffer capacity are rejected.
2023-11-24 13:42:11 +01: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 18:19:58 +02:00
pq := mustOpen ( path , name , maxPendingBytes )
2020-02-23 12:35:47 +01:00
fq := & FastQueue {
2023-11-24 13:42:11 +01:00
pq : pq ,
isPQDisabled : isPQDisabled ,
ch : make ( chan * bytesutil . ByteBuffer , maxInmemoryBlocks ) ,
2020-02-23 12:35:47 +01:00
}
fq . cond . L = & fq . mu
2020-09-16 20:13:27 +02:00
fq . lastInmemoryBlockReadTime = fasttime . UnixTimestamp ( )
2021-04-26 23:23:25 +02: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 12:03:39 +02:00
pendingBytes := fq . GetPendingBytes ( )
2024-05-10 12:09:21 +02:00
logger . Infof ( "opened fast persistent queue at %q with maxInmemoryBlocks=%d isPQDisabled=%t, it contains %d pending bytes" , path , maxInmemoryBlocks , isPQDisabled , pendingBytes )
2020-02-23 12:35:47 +01:00
return fq
}
2023-11-25 10:31:30 +01:00
// IsWriteBlocked checks if data can be pushed into fq
func ( fq * FastQueue ) IsWriteBlocked ( ) bool {
2023-11-24 13:42:11 +01: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 23:31:07 +01:00
// UnblockAllReaders unblocks all the readers.
func ( fq * FastQueue ) UnblockAllReaders ( ) {
2020-02-23 12:35:47 +01:00
fq . mu . Lock ( )
defer fq . mu . Unlock ( )
// Unblock blocked readers
2021-08-13 11:10:00 +02: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 12:35:47 +01:00
fq . cond . Broadcast ( )
2021-02-18 23:31:07 +01: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 12:35:47 +01:00
2020-02-28 18:57:39 +01:00
// flush blocks from fq.ch to fq.pq, so they can be persisted
2020-02-23 12:35:47 +01:00
fq . flushInmemoryBlocksToFileLocked ( )
2020-02-28 18:57:39 +01:00
// Close fq.pq
fq . pq . MustClose ( )
2020-02-23 12:35:47 +01:00
2020-02-28 18:57:39 +01:00
logger . Infof ( "closed fast persistent queue at %q" , fq . pq . dir )
2020-02-23 12:35:47 +01:00
}
2020-09-16 20:13:27 +02:00
func ( fq * FastQueue ) flushInmemoryBlocksToFileIfNeededLocked ( ) {
2023-11-24 13:42:11 +01:00
if len ( fq . ch ) == 0 || fq . isPQDisabled {
2020-09-16 16:30:04 +02:00
return
}
if fasttime . UnixTimestamp ( ) < fq . lastInmemoryBlockReadTime + 5 {
return
}
fq . flushInmemoryBlocksToFileLocked ( )
}
2020-02-23 12:35:47 +01:00
func ( fq * FastQueue ) flushInmemoryBlocksToFileLocked ( ) {
// fq.mu must be locked by the caller.
for len ( fq . ch ) > 0 {
bb := <- fq . ch
2020-02-28 18:57:39 +01:00
fq . pq . MustWriteBlock ( bb . B )
2020-02-23 12:35:47 +01:00
fq . pendingInmemoryBytes -= uint64 ( len ( bb . B ) )
2020-09-16 16:30:04 +02:00
fq . lastInmemoryBlockReadTime = fasttime . UnixTimestamp ( )
2020-02-23 12:35:47 +01:00
blockBufPool . Put ( bb )
}
2020-05-16 08:31:46 +02:00
// Unblock all the potentially blocked readers, so they could proceed with reading file-based queue.
fq . cond . Broadcast ( )
2020-02-23 12:35:47 +01: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 18:57:39 +01:00
n += fq . pq . GetPendingBytes ( )
2020-02-23 12:35:47 +01: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 10:31:30 +01: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 13:42:11 +01:00
func ( fq * FastQueue ) MustWriteBlockIgnoreDisabledPQ ( block [ ] byte ) {
2023-11-25 10:31:30 +01:00
if ! fq . tryWriteBlock ( block , true ) {
logger . Fatalf ( "BUG: tryWriteBlock must always write data even if persistence is disabled" )
2023-11-24 13:42:11 +01:00
}
}
2023-11-25 10:31:30 +01: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 13:42:11 +01:00
}
// WriteBlock writes block to fq.
2023-11-25 10:31:30 +01:00
func ( fq * FastQueue ) tryWriteBlock ( block [ ] byte , ignoreDisabledPQ bool ) bool {
2020-02-23 12:35:47 +01:00
fq . mu . Lock ( )
defer fq . mu . Unlock ( )
2023-11-25 10:31:30 +01:00
isPQWriteAllowed := ! fq . isPQDisabled || ignoreDisabledPQ
2023-11-24 13:42:11 +01:00
2020-09-16 20:13:27 +02:00
fq . flushInmemoryBlocksToFileIfNeededLocked ( )
2020-02-28 18:57:39 +01:00
if n := fq . pq . GetPendingBytes ( ) ; n > 0 {
2020-02-23 12:35:47 +01: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 10:31:30 +01:00
if ! isPQWriteAllowed {
return false
}
2020-02-28 18:57:39 +01:00
fq . pq . MustWriteBlock ( block )
2023-11-24 13:42:11 +01:00
return true
2020-02-23 12:35:47 +01:00
}
if len ( fq . ch ) == cap ( fq . ch ) {
2023-11-25 10:31:30 +01:00
// There is no space left in the in-memory queue. Put the data to file-based queue.
if ! isPQWriteAllowed {
2023-11-24 13:42:11 +01:00
return false
}
2020-02-23 12:35:47 +01:00
fq . flushInmemoryBlocksToFileLocked ( )
2020-02-28 18:57:39 +01:00
fq . pq . MustWriteBlock ( block )
2023-11-24 13:42:11 +01:00
return true
2020-02-23 12:35:47 +01:00
}
2023-11-25 10:31:30 +01:00
// Fast path - put the block to in-memory queue.
2020-02-23 12:35:47 +01:00
bb := blockBufPool . Get ( )
bb . B = append ( bb . B [ : 0 ] , block ... )
fq . ch <- bb
fq . pendingInmemoryBytes += uint64 ( len ( block ) )
2020-05-16 08:31:46 +02:00
// Notify potentially blocked reader.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/pull/484 for the context.
fq . cond . Signal ( )
2023-11-24 13:42:11 +01:00
return true
2020-02-23 12:35:47 +01: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 11:10:00 +02:00
if fq . stopDeadline > 0 && fasttime . UnixTimestamp ( ) > fq . stopDeadline {
2020-02-23 12:35:47 +01:00
return dst , false
}
if len ( fq . ch ) > 0 {
2020-02-28 18:57:39 +01:00
if n := fq . pq . GetPendingBytes ( ) ; n > 0 {
2020-05-16 08:31:46 +02: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 12:35:47 +01:00
}
bb := <- fq . ch
fq . pendingInmemoryBytes -= uint64 ( len ( bb . B ) )
2020-09-16 16:30:04 +02:00
fq . lastInmemoryBlockReadTime = fasttime . UnixTimestamp ( )
2020-02-23 12:35:47 +01:00
dst = append ( dst , bb . B ... )
blockBufPool . Put ( bb )
return dst , true
}
2020-02-28 18:57:39 +01:00
if n := fq . pq . GetPendingBytes ( ) ; n > 0 {
2021-04-05 18:19:58 +02:00
data , ok := fq . pq . MustReadBlockNonblocking ( dst )
if ok {
return data , true
}
dst = data
continue
2020-02-23 12:35:47 +01:00
}
2021-08-13 11:10:00 +02:00
if fq . stopDeadline > 0 {
return dst , false
}
2020-02-23 12:35:47 +01:00
// There are no blocks. Wait for new block.
2020-02-28 18:57:39 +01:00
fq . pq . ResetIfEmpty ( )
2020-02-23 12:35:47 +01:00
fq . cond . Wait ( )
}
}
2023-03-28 03:15:28 +02:00
2023-03-28 05:05:38 +02:00
// Dirname returns the directory name for persistent queue.
2023-03-28 03:33:05 +02:00
func ( fq * FastQueue ) Dirname ( ) string {
return filepath . Base ( fq . pq . dir )
2023-03-28 03:15:28 +02:00
}