github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/nbs/conjoiner.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // This file incorporates work covered by the following copyright and 16 // permission notice: 17 // 18 // Copyright 2017 Attic Labs, Inc. All rights reserved. 19 // Licensed under the Apache License, version 2.0: 20 // http://www.apache.org/licenses/LICENSE-2.0 21 22 package nbs 23 24 import ( 25 "context" 26 "errors" 27 "sort" 28 "sync" 29 "time" 30 31 "github.com/dolthub/dolt/go/store/atomicerr" 32 ) 33 34 type conjoiner interface { 35 // ConjoinRequired tells the caller whether or not it's time to request a 36 // Conjoin, based upon the contents of |ts| and the conjoiner 37 // implementation's policy. 38 ConjoinRequired(ts tableSet) bool 39 40 // Conjoin attempts to use |p| to conjoin some number of tables referenced 41 // by |upstream|, allowing it to update |mm| with a new, smaller, set of tables 42 // that references precisely the same set of chunks. Conjoin() may not 43 // actually conjoin any upstream tables, usually because some out-of- 44 // process actor has already landed a conjoin of its own. Callers must 45 // handle this, likely by rebasing against upstream and re-evaluating the 46 // situation. 47 Conjoin(ctx context.Context, upstream manifestContents, mm manifestUpdater, p tablePersister, stats *Stats) (manifestContents, error) 48 } 49 50 type inlineConjoiner struct { 51 maxTables int 52 } 53 54 func (c inlineConjoiner) ConjoinRequired(ts tableSet) bool { 55 return ts.Size() > c.maxTables && len(ts.upstream) >= 2 56 } 57 58 func (c inlineConjoiner) Conjoin(ctx context.Context, upstream manifestContents, mm manifestUpdater, p tablePersister, stats *Stats) (manifestContents, error) { 59 return conjoin(ctx, upstream, mm, p, stats) 60 } 61 62 type noopConjoiner struct { 63 } 64 65 func (c noopConjoiner) ConjoinRequired(ts tableSet) bool { 66 return false 67 } 68 69 func (c noopConjoiner) Conjoin(ctx context.Context, upstream manifestContents, mm manifestUpdater, p tablePersister, stats *Stats) (manifestContents, error) { 70 return manifestContents{}, errors.New("unsupported conjoin operation on noopConjoiner") 71 } 72 73 func conjoin(ctx context.Context, upstream manifestContents, mm manifestUpdater, p tablePersister, stats *Stats) (manifestContents, error) { 74 var conjoined tableSpec 75 var conjoinees, keepers, appendixSpecs []tableSpec 76 77 for { 78 if conjoinees == nil { 79 // Appendix table files should never be conjoined 80 // so we remove them before conjoining and add them 81 // back after 82 if upstream.NumAppendixSpecs() != 0 { 83 upstream, appendixSpecs = upstream.removeAppendixSpecs() 84 } 85 86 var err error 87 conjoined, conjoinees, keepers, err = conjoinTables(ctx, p, upstream.specs, stats) 88 89 if err != nil { 90 return manifestContents{}, err 91 } 92 } 93 94 specs := append(make([]tableSpec, 0, len(keepers)+1), conjoined) 95 if len(appendixSpecs) > 0 { 96 specs = append(make([]tableSpec, 0, len(specs)+len(appendixSpecs)), appendixSpecs...) 97 specs = append(specs, conjoined) 98 } 99 100 specs = append(specs, keepers...) 101 102 newContents := manifestContents{ 103 vers: upstream.vers, 104 root: upstream.root, 105 lock: generateLockHash(upstream.root, specs), 106 gcGen: upstream.gcGen, 107 specs: specs, 108 appendix: appendixSpecs, 109 } 110 111 var err error 112 upstream, err = mm.Update(ctx, upstream.lock, newContents, stats, nil) 113 if err != nil { 114 return manifestContents{}, err 115 } 116 117 if newContents.lock == upstream.lock { 118 return upstream, nil 119 } 120 121 // Optimistic lock failure. Someone else moved to the root, the set of tables, or both out from under us. 122 // If we can re-use the conjoin we already performed, we want to try again. Currently, we will only do so if ALL conjoinees are still present upstream. If we can't re-use...then someone else almost certainly landed a conjoin upstream. In this case, bail and let clients ask again if they think they still can't proceed. 123 124 // If the appendix has changed we simply bail 125 // and let the client retry 126 if len(appendixSpecs) > 0 { 127 if len(upstream.appendix) != len(appendixSpecs) { 128 return upstream, nil 129 } 130 for i := range upstream.appendix { 131 if upstream.appendix[i].name != appendixSpecs[i].name { 132 return upstream, nil 133 } 134 } 135 136 // No appendix change occured, so we remove the appendix 137 // on the "latest" upstream which will be added back 138 // before the conjoin completes 139 upstream, appendixSpecs = upstream.removeAppendixSpecs() 140 } 141 142 conjoineeSet := map[addr]struct{}{} 143 upstreamNames := map[addr]struct{}{} 144 for _, spec := range upstream.specs { 145 upstreamNames[spec.name] = struct{}{} 146 } 147 for _, c := range conjoinees { 148 if _, present := upstreamNames[c.name]; !present { 149 return upstream, nil // Bail! 150 } 151 conjoineeSet[c.name] = struct{}{} 152 } 153 154 // Filter conjoinees out of upstream.specs to generate new set of keepers 155 keepers = make([]tableSpec, 0, len(upstream.specs)-len(conjoinees)) 156 for _, spec := range upstream.specs { 157 if _, present := conjoineeSet[spec.name]; !present { 158 keepers = append(keepers, spec) 159 } 160 } 161 } 162 } 163 164 func conjoinTables(ctx context.Context, p tablePersister, upstream []tableSpec, stats *Stats) (conjoined tableSpec, conjoinees, keepers []tableSpec, err error) { 165 // Open all the upstream tables concurrently 166 sources := make(chunkSources, len(upstream)) 167 168 ae := atomicerr.New() 169 wg := sync.WaitGroup{} 170 for i, spec := range upstream { 171 wg.Add(1) 172 go func(idx int, spec tableSpec) { 173 defer wg.Done() 174 var err error 175 sources[idx], err = p.Open(ctx, spec.name, spec.chunkCount, stats) 176 177 ae.SetIfError(err) 178 }(i, spec) 179 i++ 180 } 181 wg.Wait() 182 183 if err := ae.Get(); err != nil { 184 return tableSpec{}, nil, nil, err 185 } 186 187 t1 := time.Now() 188 189 toConjoin, toKeep, err := chooseConjoinees(sources) 190 191 if err != nil { 192 return tableSpec{}, nil, nil, err 193 } 194 195 conjoinedSrc, err := p.ConjoinAll(ctx, toConjoin, stats) 196 197 if err != nil { 198 return tableSpec{}, nil, nil, err 199 } 200 201 stats.ConjoinLatency.SampleTimeSince(t1) 202 stats.TablesPerConjoin.SampleLen(len(toConjoin)) 203 204 cnt, err := conjoinedSrc.count() 205 206 if err != nil { 207 return tableSpec{}, nil, nil, err 208 } 209 210 stats.ChunksPerConjoin.Sample(uint64(cnt)) 211 212 conjoinees, err = toSpecs(toConjoin) 213 214 if err != nil { 215 return tableSpec{}, nil, nil, err 216 } 217 218 keepers, err = toSpecs(toKeep) 219 220 if err != nil { 221 return tableSpec{}, nil, nil, err 222 } 223 224 h, err := conjoinedSrc.hash() 225 226 if err != nil { 227 return tableSpec{}, nil, nil, err 228 } 229 230 cnt, err = conjoinedSrc.count() 231 232 if err != nil { 233 return tableSpec{}, nil, nil, err 234 } 235 236 return tableSpec{h, cnt}, conjoinees, keepers, nil 237 } 238 239 // Current approach is to choose the smallest N tables which, when removed and replaced with the conjoinment, will leave the conjoinment as the smallest table. 240 func chooseConjoinees(upstream chunkSources) (toConjoin, toKeep chunkSources, err error) { 241 sortedUpstream := make(chunkSources, len(upstream)) 242 copy(sortedUpstream, upstream) 243 244 csbac := chunkSourcesByAscendingCount{sortedUpstream, nil} 245 sort.Sort(csbac) 246 247 if csbac.err != nil { 248 return nil, nil, csbac.err 249 } 250 251 partition := 2 252 upZero, err := sortedUpstream[0].count() 253 254 if err != nil { 255 return nil, nil, err 256 } 257 258 upOne, err := sortedUpstream[1].count() 259 260 if err != nil { 261 return nil, nil, err 262 } 263 264 sum := upZero + upOne 265 for partition < len(sortedUpstream) { 266 partCnt, err := sortedUpstream[partition].count() 267 268 if err != nil { 269 return nil, nil, err 270 } 271 272 if sum <= partCnt { 273 break 274 } 275 276 sum += partCnt 277 partition++ 278 } 279 280 return sortedUpstream[:partition], sortedUpstream[partition:], nil 281 } 282 283 func toSpecs(srcs chunkSources) ([]tableSpec, error) { 284 specs := make([]tableSpec, len(srcs)) 285 for i, src := range srcs { 286 cnt, err := src.count() 287 288 if err != nil { 289 return nil, err 290 } 291 292 if cnt <= 0 { 293 return nil, errors.New("invalid table spec has no sources") 294 } 295 296 h, err := src.hash() 297 298 if err != nil { 299 return nil, err 300 } 301 302 cnt, err = src.count() 303 304 if err != nil { 305 return nil, err 306 } 307 308 specs[i] = tableSpec{h, cnt} 309 } 310 311 return specs, nil 312 }