github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/lsmkv/segment.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package lsmkv
    13  
    14  import (
    15  	"bufio"
    16  	"bytes"
    17  	"fmt"
    18  	"io"
    19  	"os"
    20  
    21  	"github.com/edsrzf/mmap-go"
    22  	"github.com/sirupsen/logrus"
    23  	"github.com/weaviate/weaviate/adapters/repos/db/lsmkv/segmentindex"
    24  	"github.com/weaviate/weaviate/entities/lsmkv"
    25  	"github.com/willf/bloom"
    26  )
    27  
    28  type segment struct {
    29  	path                string
    30  	level               uint16
    31  	secondaryIndexCount uint16
    32  	version             uint16
    33  	segmentStartPos     uint64
    34  	segmentEndPos       uint64
    35  	dataStartPos        uint64
    36  	dataEndPos          uint64
    37  	contents            []byte
    38  	contentFile         *os.File
    39  	strategy            segmentindex.Strategy
    40  	index               diskIndex
    41  	secondaryIndices    []diskIndex
    42  	logger              logrus.FieldLogger
    43  	metrics             *Metrics
    44  	size                int64
    45  	mmapContents        bool
    46  
    47  	useBloomFilter        bool // see bucket for more datails
    48  	bloomFilter           *bloom.BloomFilter
    49  	secondaryBloomFilters []*bloom.BloomFilter
    50  	bloomFilterMetrics    *bloomFilterMetrics
    51  
    52  	// the net addition this segment adds with respect to all previous segments
    53  	calcCountNetAdditions bool // see bucket for more datails
    54  	countNetAdditions     int
    55  }
    56  
    57  type diskIndex interface {
    58  	// Get return lsmkv.NotFound in case no node can be found
    59  	Get(key []byte) (segmentindex.Node, error)
    60  
    61  	// Seek returns lsmkv.NotFound in case the seek value is larger than
    62  	// the highest value in the collection, otherwise it returns the next highest
    63  	// value (or the exact value if present)
    64  	Seek(key []byte) (segmentindex.Node, error)
    65  
    66  	// AllKeys in no specific order, e.g. for building a bloom filter
    67  	AllKeys() ([][]byte, error)
    68  
    69  	// Size of the index in bytes
    70  	Size() int
    71  }
    72  
    73  func newSegment(path string, logger logrus.FieldLogger, metrics *Metrics,
    74  	existsLower existsOnLowerSegmentsFn, mmapContents bool,
    75  	useBloomFilter bool, calcCountNetAdditions bool, overwriteDerived bool,
    76  ) (*segment, error) {
    77  	file, err := os.Open(path)
    78  	if err != nil {
    79  		return nil, fmt.Errorf("open file: %w", err)
    80  	}
    81  
    82  	fileInfo, err := file.Stat()
    83  	if err != nil {
    84  		return nil, fmt.Errorf("stat file: %w", err)
    85  	}
    86  
    87  	contents, err := mmap.MapRegion(file, int(fileInfo.Size()), mmap.RDONLY, 0, 0)
    88  	if err != nil {
    89  		return nil, fmt.Errorf("mmap file: %w", err)
    90  	}
    91  
    92  	header, err := segmentindex.ParseHeader(bytes.NewReader(contents[:segmentindex.HeaderSize]))
    93  	if err != nil {
    94  		return nil, fmt.Errorf("parse header: %w", err)
    95  	}
    96  
    97  	switch header.Strategy {
    98  	case segmentindex.StrategyReplace, segmentindex.StrategySetCollection,
    99  		segmentindex.StrategyMapCollection, segmentindex.StrategyRoaringSet:
   100  	default:
   101  		return nil, fmt.Errorf("unsupported strategy in segment")
   102  	}
   103  
   104  	primaryIndex, err := header.PrimaryIndex(contents)
   105  	if err != nil {
   106  		return nil, fmt.Errorf("extract primary index position: %w", err)
   107  	}
   108  
   109  	primaryDiskIndex := segmentindex.NewDiskTree(primaryIndex)
   110  
   111  	seg := &segment{
   112  		level:                 header.Level,
   113  		path:                  path,
   114  		contents:              contents,
   115  		version:               header.Version,
   116  		secondaryIndexCount:   header.SecondaryIndices,
   117  		segmentStartPos:       header.IndexStart,
   118  		segmentEndPos:         uint64(fileInfo.Size()),
   119  		strategy:              header.Strategy,
   120  		dataStartPos:          segmentindex.HeaderSize, // fixed value that's the same for all strategies
   121  		dataEndPos:            header.IndexStart,
   122  		index:                 primaryDiskIndex,
   123  		logger:                logger,
   124  		metrics:               metrics,
   125  		size:                  fileInfo.Size(),
   126  		mmapContents:          mmapContents,
   127  		useBloomFilter:        useBloomFilter,
   128  		calcCountNetAdditions: calcCountNetAdditions,
   129  	}
   130  
   131  	// Using pread strategy requires file to remain open for segment lifetime
   132  	if seg.mmapContents {
   133  		defer file.Close()
   134  	} else {
   135  		seg.contentFile = file
   136  	}
   137  
   138  	if seg.secondaryIndexCount > 0 {
   139  		seg.secondaryIndices = make([]diskIndex, seg.secondaryIndexCount)
   140  		for i := range seg.secondaryIndices {
   141  			secondary, err := header.SecondaryIndex(contents, uint16(i))
   142  			if err != nil {
   143  				return nil, fmt.Errorf("get position for secondary index at %d: %w", i, err)
   144  			}
   145  			seg.secondaryIndices[i] = segmentindex.NewDiskTree(secondary)
   146  		}
   147  	}
   148  
   149  	if seg.useBloomFilter {
   150  		if err := seg.initBloomFilters(metrics, overwriteDerived); err != nil {
   151  			return nil, err
   152  		}
   153  	}
   154  	if seg.calcCountNetAdditions {
   155  		if err := seg.initCountNetAdditions(existsLower, overwriteDerived); err != nil {
   156  			return nil, err
   157  		}
   158  	}
   159  
   160  	return seg, nil
   161  }
   162  
   163  func (s *segment) close() error {
   164  	var munmapErr, fileCloseErr error
   165  
   166  	m := mmap.MMap(s.contents)
   167  	munmapErr = m.Unmap()
   168  	if s.contentFile != nil {
   169  		fileCloseErr = s.contentFile.Close()
   170  	}
   171  
   172  	if munmapErr != nil || fileCloseErr != nil {
   173  		return fmt.Errorf("close segment: munmap: %v, close contents file: %w", munmapErr, fileCloseErr)
   174  	}
   175  
   176  	return nil
   177  }
   178  
   179  func (s *segment) drop() error {
   180  	// support for persisting bloom filters and cnas was added in v1.17,
   181  	// therefore the files may not be present on segments created with previous
   182  	// versions. By using RemoveAll, which does not error on NotExists, these
   183  	// drop calls are backward-compatible:
   184  	if err := os.RemoveAll(s.bloomFilterPath()); err != nil {
   185  		return fmt.Errorf("drop bloom filter: %w", err)
   186  	}
   187  
   188  	for i := 0; i < int(s.secondaryIndexCount); i++ {
   189  		if err := os.RemoveAll(s.bloomFilterSecondaryPath(i)); err != nil {
   190  			return fmt.Errorf("drop bloom filter: %w", err)
   191  		}
   192  	}
   193  
   194  	if err := os.RemoveAll(s.countNetPath()); err != nil {
   195  		return fmt.Errorf("drop count net additions file: %w", err)
   196  	}
   197  
   198  	// for the segment itself, we're not using RemoveAll, but Remove. If there
   199  	// was a NotExists error here, something would be seriously wrong, and we
   200  	// don't want to ignore it.
   201  	if err := os.Remove(s.path); err != nil {
   202  		return fmt.Errorf("drop segment: %w", err)
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  // Size returns the total size of the segment in bytes, including the header
   209  // and index
   210  func (s *segment) Size() int {
   211  	return int(s.size)
   212  }
   213  
   214  // PayloadSize is only the payload of the index, excluding the index
   215  func (s *segment) PayloadSize() int {
   216  	return int(s.dataEndPos)
   217  }
   218  
   219  type nodeReader struct {
   220  	r io.Reader
   221  }
   222  
   223  func (n *nodeReader) Read(b []byte) (int, error) {
   224  	return n.r.Read(b)
   225  }
   226  
   227  type nodeOffset struct {
   228  	start, end uint64
   229  }
   230  
   231  func (s *segment) newNodeReader(offset nodeOffset) (*nodeReader, error) {
   232  	var (
   233  		r   io.Reader
   234  		err error
   235  	)
   236  	if s.mmapContents {
   237  		contents := s.contents[offset.start:]
   238  		if offset.end != 0 {
   239  			contents = s.contents[offset.start:offset.end]
   240  		}
   241  		r, err = s.bytesReaderFrom(contents)
   242  	} else {
   243  		r, err = s.bufferedReaderAt(offset.start)
   244  	}
   245  	if err != nil {
   246  		return nil, fmt.Errorf("new nodeReader: %w", err)
   247  	}
   248  	return &nodeReader{r: r}, nil
   249  }
   250  
   251  func (s *segment) copyNode(b []byte, offset nodeOffset) error {
   252  	if s.mmapContents {
   253  		copy(b, s.contents[offset.start:offset.end])
   254  		return nil
   255  	}
   256  	n, err := s.newNodeReader(offset)
   257  	if err != nil {
   258  		return fmt.Errorf("copy node: %w", err)
   259  	}
   260  	_, err = io.ReadFull(n, b)
   261  	return err
   262  }
   263  
   264  func (s *segment) bytesReaderFrom(in []byte) (*bytes.Reader, error) {
   265  	if len(in) == 0 {
   266  		return nil, lsmkv.NotFound
   267  	}
   268  	return bytes.NewReader(in), nil
   269  }
   270  
   271  func (s *segment) bufferedReaderAt(offset uint64) (*bufio.Reader, error) {
   272  	if s.contentFile == nil {
   273  		return nil, fmt.Errorf("nil contentFile for segment at %s", s.path)
   274  	}
   275  
   276  	r := io.NewSectionReader(s.contentFile, int64(offset), s.size)
   277  	return bufio.NewReader(r), nil
   278  }