github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/vector/hnsw/config_update.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 "os" 16 "sync/atomic" 17 18 enterrors "github.com/weaviate/weaviate/entities/errors" 19 "github.com/weaviate/weaviate/usecases/configbase" 20 21 "github.com/pkg/errors" 22 "github.com/weaviate/weaviate/entities/schema" 23 ent "github.com/weaviate/weaviate/entities/vectorindex/hnsw" 24 ) 25 26 func ValidateUserConfigUpdate(initial, updated schema.VectorIndexConfig) error { 27 initialParsed, ok := initial.(ent.UserConfig) 28 if !ok { 29 return errors.Errorf("initial is not UserConfig, but %T", initial) 30 } 31 32 updatedParsed, ok := updated.(ent.UserConfig) 33 if !ok { 34 return errors.Errorf("updated is not UserConfig, but %T", updated) 35 } 36 37 immutableFields := []immutableParameter{ 38 { 39 name: "efConstruction", 40 accessor: func(c ent.UserConfig) interface{} { return c.EFConstruction }, 41 }, 42 { 43 name: "maxConnections", 44 accessor: func(c ent.UserConfig) interface{} { return c.MaxConnections }, 45 }, 46 { 47 // NOTE: There isn't a technical reason for this to be immutable, it 48 // simply hasn't been implemented yet. It would require to stop the 49 // current timer and start a new one. Certainly possible, but let's see 50 // if anyone actually needs this before implementing it. 51 name: "cleanupIntervalSeconds", 52 accessor: func(c ent.UserConfig) interface{} { return c.CleanupIntervalSeconds }, 53 }, 54 { 55 name: "distance", 56 accessor: func(c ent.UserConfig) interface{} { return c.Distance }, 57 }, 58 } 59 60 for _, u := range immutableFields { 61 if err := validateImmutableField(u, initialParsed, updatedParsed); err != nil { 62 return err 63 } 64 } 65 66 return nil 67 } 68 69 type immutableParameter struct { 70 accessor func(c ent.UserConfig) interface{} 71 name string 72 } 73 74 func validateImmutableField(u immutableParameter, 75 previous, next ent.UserConfig, 76 ) error { 77 oldField := u.accessor(previous) 78 newField := u.accessor(next) 79 if oldField != newField { 80 return errors.Errorf("%s is immutable: attempted change from \"%v\" to \"%v\"", 81 u.name, oldField, newField) 82 } 83 84 return nil 85 } 86 87 func (h *hnsw) UpdateUserConfig(updated schema.VectorIndexConfig, callback func()) error { 88 parsed, ok := updated.(ent.UserConfig) 89 if !ok { 90 callback() 91 return errors.Errorf("config is not UserConfig, but %T", updated) 92 } 93 94 // Store automatically as a lock here would be very expensive, this value is 95 // read on every single user-facing search, which can be highly concurrent 96 atomic.StoreInt64(&h.ef, int64(parsed.EF)) 97 atomic.StoreInt64(&h.efMin, int64(parsed.DynamicEFMin)) 98 atomic.StoreInt64(&h.efMax, int64(parsed.DynamicEFMax)) 99 atomic.StoreInt64(&h.efFactor, int64(parsed.DynamicEFFactor)) 100 atomic.StoreInt64(&h.flatSearchCutoff, int64(parsed.FlatSearchCutoff)) 101 102 if !parsed.PQ.Enabled && !parsed.BQ.Enabled { 103 callback() 104 return nil 105 } 106 107 h.pqConfig = parsed.PQ 108 if asyncEnabled() { 109 callback() 110 return nil 111 } 112 113 if !h.compressed.Load() { 114 // the compression will fire the callback once it's complete 115 return h.TurnOnCompression(callback) 116 } else { 117 h.compressor.SetCacheMaxSize(int64(parsed.VectorCacheMaxObjects)) 118 callback() 119 return nil 120 } 121 } 122 123 func asyncEnabled() bool { 124 return configbase.Enabled(os.Getenv("ASYNC_INDEXING")) 125 } 126 127 func (h *hnsw) TurnOnCompression(callback func()) error { 128 h.logger.WithField("action", "compress").Info("switching to compressed vectors") 129 130 err := ent.ValidatePQConfig(h.pqConfig) 131 if err != nil { 132 callback() 133 return err 134 } 135 136 enterrors.GoWrapper(func() { h.compressThenCallback(callback) }, h.logger) 137 138 return nil 139 } 140 141 func (h *hnsw) compressThenCallback(callback func()) { 142 defer callback() 143 144 uc := ent.UserConfig{ 145 PQ: h.pqConfig, 146 BQ: ent.BQConfig{ 147 Enabled: !h.pqConfig.Enabled, 148 }, 149 } 150 if err := h.compress(uc); err != nil { 151 h.logger.Error(err) 152 return 153 } 154 h.logger.WithField("action", "compress").Info("vector compression complete") 155 }