github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/index/results.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package index 22 23 import ( 24 "errors" 25 "sync" 26 27 "github.com/m3db/m3/src/m3ninx/doc" 28 "github.com/m3db/m3/src/m3ninx/index/segment/fst/encoding/docs" 29 "github.com/m3db/m3/src/x/ident" 30 ) 31 32 var ( 33 errUnableToAddResultMissingID = errors.New("no id for result") 34 resultMapNoFinalizeOpts = ResultsMapSetUnsafeOptions{ 35 NoCopyKey: true, 36 NoFinalizeKey: true, 37 } 38 ) 39 40 type results struct { 41 sync.RWMutex 42 43 nsID ident.ID 44 opts QueryResultsOptions 45 46 reusableID *ident.ReusableBytesID 47 resultsMap *ResultsMap 48 totalDocsCount int 49 50 // Utilization stats, do not reset. 51 resultsUtilizationStats resultsUtilizationStats 52 53 idPool ident.Pool 54 pool QueryResultsPool 55 } 56 57 // NewQueryResults returns a new query results object. 58 func NewQueryResults( 59 namespaceID ident.ID, 60 opts QueryResultsOptions, 61 indexOpts Options, 62 ) QueryResults { 63 return &results{ 64 nsID: namespaceID, 65 opts: opts, 66 resultsMap: newResultsMap(), 67 idPool: indexOpts.IdentifierPool(), 68 pool: indexOpts.QueryResultsPool(), 69 reusableID: ident.NewReusableBytesID(), 70 } 71 } 72 73 func (r *results) EnforceLimits() bool { return true } 74 75 func (r *results) Reset(nsID ident.ID, opts QueryResultsOptions) { 76 r.Lock() 77 78 // Finalize existing held nsID. 79 if r.nsID != nil { 80 r.nsID.Finalize() 81 } 82 // Make an independent copy of the new nsID. 83 if nsID != nil { 84 nsID = r.idPool.Clone(nsID) 85 } 86 r.nsID = nsID 87 88 // Reset all keys in the map next, this will finalize the keys. 89 r.resultsMap.Reset() 90 r.totalDocsCount = 0 91 92 r.opts = opts 93 94 r.Unlock() 95 } 96 97 // NB: If documents with duplicate IDs are added, they are simply ignored and 98 // the first document added with an ID is returned. 99 func (r *results) AddDocuments(batch []doc.Document) (int, int, error) { 100 r.Lock() 101 err := r.addDocumentsBatchWithLock(batch) 102 size := r.resultsMap.Len() 103 docsCount := r.totalDocsCount + len(batch) 104 r.totalDocsCount = docsCount 105 r.Unlock() 106 return size, docsCount, err 107 } 108 109 func (r *results) addDocumentsBatchWithLock(batch []doc.Document) error { 110 for i := range batch { 111 _, size, err := r.addDocumentWithLock(batch[i]) 112 if err != nil { 113 return err 114 } 115 if r.opts.SizeLimit > 0 && size >= r.opts.SizeLimit { 116 // Early return if limit enforced and we hit our limit. 117 break 118 } 119 } 120 return nil 121 } 122 123 func (r *results) addDocumentWithLock(w doc.Document) (bool, int, error) { 124 id, err := docs.ReadIDFromDocument(w) 125 if err != nil { 126 return false, r.resultsMap.Len(), err 127 } 128 129 if len(id) == 0 { 130 return false, r.resultsMap.Len(), errUnableToAddResultMissingID 131 } 132 133 // Need to apply filter if set first. 134 r.reusableID.Reset(id) 135 if r.opts.FilterID != nil && !r.opts.FilterID(r.reusableID) { 136 return false, r.resultsMap.Len(), nil 137 } 138 139 // check if it already exists in the map. 140 if r.resultsMap.Contains(id) { 141 return false, r.resultsMap.Len(), nil 142 } 143 144 // It is assumed that the document is valid for the lifetime of the index 145 // results. 146 r.resultsMap.SetUnsafe(id, w, resultMapNoFinalizeOpts) 147 148 return true, r.resultsMap.Len(), nil 149 } 150 151 func (r *results) Namespace() ident.ID { 152 r.RLock() 153 v := r.nsID 154 r.RUnlock() 155 return v 156 } 157 158 func (r *results) Map() *ResultsMap { 159 r.RLock() 160 v := r.resultsMap 161 r.RUnlock() 162 return v 163 } 164 165 func (r *results) Size() int { 166 r.RLock() 167 v := r.resultsMap.Len() 168 r.RUnlock() 169 return v 170 } 171 172 func (r *results) TotalDocsCount() int { 173 r.RLock() 174 count := r.totalDocsCount 175 r.RUnlock() 176 return count 177 } 178 179 func (r *results) Finalize() { 180 r.Lock() 181 returnToPool := r.resultsUtilizationStats.updateAndCheck(r.totalDocsCount) 182 r.Unlock() 183 184 // Reset locks so cannot hold onto lock for call to Finalize. 185 r.Reset(nil, QueryResultsOptions{}) 186 187 if r.pool != nil && returnToPool { 188 r.pool.Put(r) 189 } 190 }