package storage import ( "container/heap" "fmt" "io" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil" ) // partitionSearch represents a search in the partition. type partitionSearch struct { // BlockRef is the block found after NextBlock call. BlockRef *BlockRef // pt is a partition to search. pt *partition // pws hold parts snapshot for the given partition during Init call. // This snapshot is used for calling Part.PutParts on partitionSearch.MustClose. pws []*partWrapper psPool []partSearch psHeap partSearchHeap err error nextBlockNoop bool needClosing bool } func (pts *partitionSearch) reset() { pts.BlockRef = nil pts.pt = nil for i := range pts.pws { pts.pws[i] = nil } pts.pws = pts.pws[:0] for i := range pts.psPool { pts.psPool[i].reset() } pts.psPool = pts.psPool[:0] for i := range pts.psHeap { pts.psHeap[i] = nil } pts.psHeap = pts.psHeap[:0] pts.err = nil pts.nextBlockNoop = false pts.needClosing = false } // Init initializes the search in the given partition for the given tsid and tr. // // tsids must be sorted. // tsids cannot be modified after the Init call, since it is owned by pts. // // MustClose must be called when partition search is done. func (pts *partitionSearch) Init(pt *partition, tsids []TSID, tr TimeRange) { if pts.needClosing { logger.Panicf("BUG: missing partitionSearch.MustClose call before the next call to Init") } pts.reset() pts.pt = pt pts.needClosing = true if len(tsids) == 0 { // Fast path - zero tsids. pts.err = io.EOF return } if pt.tr.MinTimestamp > tr.MaxTimestamp || pt.tr.MaxTimestamp < tr.MinTimestamp { // Fast path - the partition doesn't contain rows for the given time range. pts.err = io.EOF return } pts.pws = pt.GetParts(pts.pws[:0], true) // Initialize psPool. pts.psPool = slicesutil.SetLength(pts.psPool, len(pts.pws)) for i, pw := range pts.pws { pts.psPool[i].Init(pw.p, tsids, tr) } // Initialize the psHeap. pts.psHeap = pts.psHeap[:0] for i := range pts.psPool { ps := &pts.psPool[i] if !ps.NextBlock() { if err := ps.Error(); err != nil { // Return only the first error, since it has no sense in returning all errors. pts.err = fmt.Errorf("cannot initialize partition search: %w", err) return } continue } pts.psHeap = append(pts.psHeap, ps) } if len(pts.psHeap) == 0 { pts.err = io.EOF return } heap.Init(&pts.psHeap) pts.BlockRef = &pts.psHeap[0].BlockRef pts.nextBlockNoop = true } // NextBlock advances to the next block. // // The blocks are sorted by (TDIS, MinTimestamp). Two subsequent blocks // for the same TSID may contain overlapped time ranges. func (pts *partitionSearch) NextBlock() bool { if pts.err != nil { return false } if pts.nextBlockNoop { pts.nextBlockNoop = false return true } pts.err = pts.nextBlock() if pts.err != nil { if pts.err != io.EOF { pts.err = fmt.Errorf("cannot obtain the next block to search in the partition: %w", pts.err) } return false } return true } func (pts *partitionSearch) nextBlock() error { psMin := pts.psHeap[0] if psMin.NextBlock() { heap.Fix(&pts.psHeap, 0) pts.BlockRef = &pts.psHeap[0].BlockRef return nil } if err := psMin.Error(); err != nil { return err } heap.Pop(&pts.psHeap) if len(pts.psHeap) == 0 { return io.EOF } pts.BlockRef = &pts.psHeap[0].BlockRef return nil } func (pts *partitionSearch) Error() error { if pts.err == io.EOF { return nil } return pts.err } // MustClose closes the pts. func (pts *partitionSearch) MustClose() { if !pts.needClosing { logger.Panicf("BUG: missing Init call before the MustClose call") } pts.pt.PutParts(pts.pws) pts.reset() } type partSearchHeap []*partSearch func (psh *partSearchHeap) Len() int { return len(*psh) } func (psh *partSearchHeap) Less(i, j int) bool { x := *psh return x[i].BlockRef.bh.Less(&x[j].BlockRef.bh) } func (psh *partSearchHeap) Swap(i, j int) { x := *psh x[i], x[j] = x[j], x[i] } func (psh *partSearchHeap) Push(x any) { *psh = append(*psh, x.(*partSearch)) } func (psh *partSearchHeap) Pop() any { a := *psh v := a[len(a)-1] *psh = a[:len(a)-1] return v }