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 }