github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/vector/hnsw/vertex.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 hnsw
    13  
    14  import (
    15  	"sync"
    16  )
    17  
    18  type vertex struct {
    19  	id uint64
    20  	sync.Mutex
    21  	level       int
    22  	connections [][]uint64
    23  	maintenance bool
    24  }
    25  
    26  func (v *vertex) markAsMaintenance() {
    27  	v.Lock()
    28  	v.maintenance = true
    29  	v.Unlock()
    30  }
    31  
    32  func (v *vertex) unmarkAsMaintenance() {
    33  	v.Lock()
    34  	v.maintenance = false
    35  	v.Unlock()
    36  }
    37  
    38  func (v *vertex) isUnderMaintenance() bool {
    39  	v.Lock()
    40  	m := v.maintenance
    41  	v.Unlock()
    42  	return m
    43  }
    44  
    45  func (v *vertex) connectionsAtLevelNoLock(level int) []uint64 {
    46  	return v.connections[level]
    47  }
    48  
    49  func (v *vertex) upgradeToLevelNoLock(level int) {
    50  	newConnections := make([][]uint64, level+1)
    51  	copy(newConnections, v.connections)
    52  	v.level = level
    53  	v.connections = newConnections
    54  }
    55  
    56  func (v *vertex) setConnectionsAtLevel(level int, connections []uint64) {
    57  	v.Lock()
    58  	defer v.Unlock()
    59  
    60  	// before we simply copy the connections let's check how much smaller the new
    61  	// list is. If it's considerably smaller, we might want to downsize the
    62  	// current allocation
    63  	oldCap := cap(v.connections[level])
    64  	newLen := len(connections)
    65  	ratio := float64(1) - float64(newLen)/float64(oldCap)
    66  	if ratio > 0.33 || oldCap < newLen {
    67  		// the replaced slice is over 33% smaller than the last one, this makes it
    68  		// worth to replace it entirely. This has a small performance cost, it
    69  		// means that if we need append to this node again, we need to re-allocate,
    70  		// but we gain at least a 33% memory saving on this particular node right
    71  		// away.
    72  		v.connections[level] = connections
    73  		return
    74  	}
    75  
    76  	v.connections[level] = v.connections[level][:newLen]
    77  	copy(v.connections[level], connections)
    78  }
    79  
    80  func (v *vertex) appendConnectionAtLevelNoLock(level int, connection uint64, maxConns int) {
    81  	if len(v.connections[level]) == cap(v.connections[level]) {
    82  		// if the len is the capacity, this  means a new array needs to be
    83  		// allocated to back this slice. The go runtime would do this
    84  		// automatically, if we just use 'append', but it wouldn't do it very
    85  		// efficiently. It would always double the existing capacity. Since we have
    86  		// a hard limit (maxConns), we don't ever want to grow it beyond that. In
    87  		// the worst case, the current len & cap could be maxConns-1, which would
    88  		// mean we would double to 2*(maxConns-1) which would be way too large.
    89  		//
    90  		// Instead let's grow in 4 steps: 25%, 50%, 75% or full capacity
    91  		ratio := float64(len(v.connections[level])) / float64(maxConns)
    92  
    93  		target := 0
    94  		switch {
    95  		case ratio < 0.25:
    96  			target = int(float64(0.25) * float64(maxConns))
    97  		case ratio < 0.50:
    98  			target = int(float64(0.50) * float64(maxConns))
    99  		case ratio < 0.75:
   100  			target = int(float64(0.75) * float64(maxConns))
   101  		default:
   102  			target = maxConns
   103  		}
   104  
   105  		// handle rounding errors on maxConns not cleanly divisible by 4
   106  		if target < len(v.connections[level])+1 {
   107  			target = len(v.connections[level]) + 1
   108  		}
   109  
   110  		newConns := make([]uint64, len(v.connections[level]), target)
   111  		copy(newConns, v.connections[level])
   112  		v.connections[level] = newConns
   113  	}
   114  
   115  	v.connections[level] = append(v.connections[level], connection)
   116  }
   117  
   118  func (v *vertex) resetConnectionsAtLevelNoLock(level int) {
   119  	v.connections[level] = v.connections[level][:0]
   120  }