github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/roaringset/serialization.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 roaringset 13 14 import ( 15 "encoding/binary" 16 "fmt" 17 "io" 18 "math" 19 20 "github.com/weaviate/sroar" 21 "github.com/weaviate/weaviate/adapters/repos/db/lsmkv/segmentindex" 22 "github.com/weaviate/weaviate/usecases/byteops" 23 ) 24 25 // SegmentNode stores one Key-Value pair (without its index) in 26 // the LSM Segment. It uses a single []byte internally. As a result there is 27 // no decode step required at runtime. Instead you can use 28 // 29 // - [*SegmentNode.Additions] 30 // - [*SegmentNode.AdditionsWithCopy] 31 // - [*SegmentNode.Deletions] 32 // - [*SegmentNode.DeletionsWithCopy] 33 // - [*SegmentNode.PrimaryKey] 34 // 35 // to access the contents. Those helpers in turn do not require a decoding 36 // step. The accessor methods that return Roaring Bitmaps only point to 37 // existing memory (methods without WithCopy suffix), or in the worst case copy 38 // one byte slice (methods with WithCopy suffix). 39 // 40 // This makes the SegmentNode very fast to access at query time, even when it 41 // contains a large amount of data. 42 // 43 // The internal structure of the data is: 44 // 45 // byte begin-start | description 46 // --------------------|----------------------------------------------------- 47 // 0-8 | uint64 indicating the total length of the node, 48 // | this is used in cursors to identify the next node. 49 // 8-16 | uint64 length indicator for additions sraor bm -> x 50 // 16-(x+16) | additions bitmap 51 // (x+16)-(x+24) | uint64 length indicator for deletions sroar bm -> y 52 // (x+24)-(x+y+24) | deletions bitmap 53 // (x+y+24)-(x+y+28) | uint32 length indicator for primary key length -> z 54 // (x+y+28)-(x+y+z+28) | primary key 55 type SegmentNode struct { 56 data []byte 57 } 58 59 // Len indicates the total length of the [SegmentNode]. When reading multiple 60 // segments back-2-back, such as in a cursor situation, the offset of element 61 // (n+1) is the offset of element n + Len() 62 func (sn *SegmentNode) Len() uint64 { 63 return binary.LittleEndian.Uint64(sn.data[0:8]) 64 } 65 66 // Additions returns the additions roaring bitmap with shared state. Only use 67 // this method if you can guarantee that you will only use it while holding a 68 // maintenance lock or can otherwise be sure that no compaction can occur. If 69 // you can't guarantee that, instead use [*SegmentNode.AdditionsWithCopy]. 70 func (sn *SegmentNode) Additions() *sroar.Bitmap { 71 rw := byteops.NewReadWriter(sn.data) 72 rw.MoveBufferToAbsolutePosition(8) 73 return sroar.FromBuffer(rw.ReadBytesFromBufferWithUint64LengthIndicator()) 74 } 75 76 // AdditionsWithCopy returns the additions roaring bitmap without sharing state. It 77 // creates a copy of the underlying buffer. This is safe to use indefinitely, 78 // but much slower than [*SegmentNode.Additions] as it requires copying all the 79 // memory. If you know that you will only need the contents of the node for a 80 // duration of time where a lock is held that prevents compactions, it is more 81 // efficient to use [*SegmentNode.Additions]. 82 func (sn *SegmentNode) AdditionsWithCopy() *sroar.Bitmap { 83 rw := byteops.NewReadWriter(sn.data) 84 rw.MoveBufferToAbsolutePosition(8) 85 return sroar.FromBufferWithCopy(rw.ReadBytesFromBufferWithUint64LengthIndicator()) 86 } 87 88 // Deletions returns the deletions roaring bitmap with shared state. Only use 89 // this method if you can guarantee that you will only use it while holding a 90 // maintenance lock or can otherwise be sure that no compaction can occur. If 91 // you can't guarantee that, instead use [*SegmentNode.DeletionsWithCopy]. 92 func (sn *SegmentNode) Deletions() *sroar.Bitmap { 93 rw := byteops.NewReadWriter(sn.data) 94 rw.MoveBufferToAbsolutePosition(8) 95 rw.DiscardBytesFromBufferWithUint64LengthIndicator() 96 return sroar.FromBuffer(rw.ReadBytesFromBufferWithUint64LengthIndicator()) 97 } 98 99 // DeletionsWithCopy returns the deletions roaring bitmap without sharing state. It 100 // creates a copy of the underlying buffer. This is safe to use indefinitely, 101 // but much slower than [*SegmentNode.Deletions] as it requires copying all the 102 // memory. If you know that you will only need the contents of the node for a 103 // duration of time where a lock is held that prevents compactions, it is more 104 // efficient to use [*SegmentNode.Deletions]. 105 func (sn *SegmentNode) DeletionsWithCopy() *sroar.Bitmap { 106 rw := byteops.NewReadWriter(sn.data) 107 rw.MoveBufferToAbsolutePosition(8) 108 rw.DiscardBytesFromBufferWithUint64LengthIndicator() 109 return sroar.FromBufferWithCopy(rw.ReadBytesFromBufferWithUint64LengthIndicator()) 110 } 111 112 func (sn *SegmentNode) PrimaryKey() []byte { 113 rw := byteops.NewReadWriter(sn.data) 114 rw.MoveBufferToAbsolutePosition(8) 115 rw.DiscardBytesFromBufferWithUint64LengthIndicator() 116 rw.DiscardBytesFromBufferWithUint64LengthIndicator() 117 return rw.ReadBytesFromBufferWithUint32LengthIndicator() 118 } 119 120 func NewSegmentNode( 121 key []byte, additions, deletions *sroar.Bitmap, 122 ) (*SegmentNode, error) { 123 if len(key) > math.MaxUint32 { 124 return nil, fmt.Errorf("key too long, max length is %d", math.MaxUint32) 125 } 126 127 additionsBuf := additions.ToBuffer() 128 deletionsBuf := deletions.ToBuffer() 129 130 // offset + 2*uint64 length indicators + uint32 length indicator + payloads 131 expectedSize := 8 + 8 + 8 + 4 + len(additionsBuf) + len(deletionsBuf) + len(key) 132 sn := SegmentNode{ 133 data: make([]byte, expectedSize), 134 } 135 136 rw := byteops.NewReadWriter(sn.data) 137 138 // reserve the first 8 bytes for the offset, which we will write at the very 139 // end 140 rw.MoveBufferPositionForward(8) 141 if err := rw.CopyBytesToBufferWithUint64LengthIndicator(additionsBuf); err != nil { 142 return nil, err 143 } 144 145 if err := rw.CopyBytesToBufferWithUint64LengthIndicator(deletionsBuf); err != nil { 146 return nil, err 147 } 148 149 if err := rw.CopyBytesToBufferWithUint32LengthIndicator(key); err != nil { 150 return nil, err 151 } 152 153 offset := rw.Position 154 rw.MoveBufferToAbsolutePosition(0) 155 rw.WriteUint64(uint64(offset)) 156 157 return &sn, nil 158 } 159 160 // ToBuffer returns the internal buffer without copying data. Only use this, 161 // when you can be sure that it's safe to share the data, or create your own 162 // copy. 163 // 164 // It truncates the buffer at is own length, in case it was initialized with a 165 // long buffer that only had a beginning offset, but no end. Such a situation 166 // may occur with cursors. If we then returned the whole buffer and don't know 167 // what the caller plans on doing with the data, we risk passing around too 168 // much memory. Truncating at the length prevents this and has no other 169 // negative effects. 170 func (sn *SegmentNode) ToBuffer() []byte { 171 return sn.data[:sn.Len()] 172 } 173 174 // NewSegmentNodeFromBuffer creates a new segment node by using the underlying 175 // buffer without copying data. Only use this when you can be sure that it's 176 // safe to share the data or create your own copy. 177 func NewSegmentNodeFromBuffer(buf []byte) *SegmentNode { 178 return &SegmentNode{data: buf} 179 } 180 181 // KeyIndexAndWriteTo is a helper to flush a memtables full of SegmentNodes. It 182 // writes itself into the given writer and returns a [segmentindex.Key] with 183 // start and end indicators (respecting SegmentNode.Offset). Those keys can 184 // then be used to build an index for the nodes. The combination of index and 185 // node make up an LSM segment. 186 // 187 // RoaringSets do not support secondary keys, thus the segmentindex.Key will 188 // only ever contain a primary key. 189 func (sn *SegmentNode) KeyIndexAndWriteTo(w io.Writer, offset int) (segmentindex.Key, error) { 190 out := segmentindex.Key{} 191 192 n, err := w.Write(sn.data) 193 if err != nil { 194 return out, err 195 } 196 197 out.ValueStart = offset 198 out.ValueEnd = offset + n 199 out.Key = sn.PrimaryKey() 200 201 return out, nil 202 }