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  }