github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/segment/query/metadata/metadata.go (about) 1 /* 2 Copyright 2023. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package metadata 18 19 import ( 20 "errors" 21 "sort" 22 "sync" 23 "time" 24 25 dtu "github.com/siglens/siglens/pkg/common/dtypeutils" 26 "github.com/siglens/siglens/pkg/config" 27 "github.com/siglens/siglens/pkg/querytracker" 28 "github.com/siglens/siglens/pkg/segment/pqmr" 29 "github.com/siglens/siglens/pkg/segment/structs" 30 "github.com/siglens/siglens/pkg/segment/utils" 31 log "github.com/sirupsen/logrus" 32 ) 33 34 var GlobalSegStoreSummary = &structs.AllSegStoreSummary{} 35 36 // Holder struct for all rotated Metadata that exists in the server 37 type allSegmentMetadata struct { 38 39 // all SegmentMicroIndex in sorted order of descending latest time, used for global memory limiting 40 allSegmentMicroIndex []*SegmentMicroIndex 41 42 // reverse index which maps a segment key to the corresponding SegmentMicroIndex for quick access (RRC generation/search/etc.) 43 segmentMetadataReverseIndex map[string]*SegmentMicroIndex 44 45 // maps a tableName to the sorted list of SegmentMicroIndex (descending by latest time) used for initial time query filtering 46 tableSortedMetadata map[string][]*SegmentMicroIndex 47 48 // metadata update lock 49 updateLock *sync.RWMutex 50 } 51 52 var globalMetadata *allSegmentMetadata = &allSegmentMetadata{ 53 allSegmentMicroIndex: make([]*SegmentMicroIndex, 0), 54 segmentMetadataReverseIndex: make(map[string]*SegmentMicroIndex), 55 tableSortedMetadata: make(map[string][]*SegmentMicroIndex), 56 updateLock: &sync.RWMutex{}, 57 } 58 59 func BulkAddSegmentMicroIndex(allMetadata []*SegmentMicroIndex) { 60 globalMetadata.bulkAddSegmentMicroIndex(allMetadata) 61 } 62 63 func (hm *allSegmentMetadata) bulkAddSegmentMicroIndex(allMetadata []*SegmentMicroIndex) { 64 hm.updateLock.Lock() 65 defer hm.updateLock.Unlock() 66 67 for _, newSegMeta := range allMetadata { 68 if segMeta, ok := hm.segmentMetadataReverseIndex[newSegMeta.SegmentKey]; ok { 69 res, err := mergeSegmentMicroIndex(segMeta, newSegMeta) 70 if err != nil { 71 log.Errorf("BulkAddSegmentInfo: Failed to do union for segKey=%v err=%v", segMeta.SegmentKey, err) 72 continue 73 } 74 hm.segmentMetadataReverseIndex[newSegMeta.SegmentKey] = res 75 continue 76 } 77 hm.allSegmentMicroIndex = append(hm.allSegmentMicroIndex, newSegMeta) 78 hm.segmentMetadataReverseIndex[newSegMeta.SegmentKey] = newSegMeta 79 GlobalSegStoreSummary.IncrementTotalSegmentCount() 80 81 if _, ok := hm.tableSortedMetadata[newSegMeta.VirtualTableName]; !ok { 82 GlobalSegStoreSummary.IncrementTotalTableCount() 83 hm.tableSortedMetadata[newSegMeta.VirtualTableName] = make([]*SegmentMicroIndex, 0) 84 } 85 tableMetadata := hm.tableSortedMetadata[newSegMeta.VirtualTableName] 86 tableMetadata = append(tableMetadata, newSegMeta) 87 hm.tableSortedMetadata[newSegMeta.VirtualTableName] = tableMetadata 88 } 89 sort.Slice(hm.allSegmentMicroIndex, func(i, j int) bool { 90 return hm.allSegmentMicroIndex[i].LatestEpochMS > hm.allSegmentMicroIndex[j].LatestEpochMS 91 }) 92 93 for tName, tableSegmentMeta := range hm.tableSortedMetadata { 94 sort.Slice(tableSegmentMeta, func(i, j int) bool { 95 return tableSegmentMeta[i].LatestEpochMS > tableSegmentMeta[j].LatestEpochMS 96 }) 97 hm.tableSortedMetadata[tName] = tableSegmentMeta 98 } 99 } 100 101 func mergeSegmentMicroIndex(left *SegmentMicroIndex, right *SegmentMicroIndex) (*SegmentMicroIndex, error) { 102 103 if left.SegmentKey != right.SegmentKey { 104 return left, errors.New("left and right keys were not same") 105 } 106 if right.EarliestEpochMS != 0 && left.EarliestEpochMS == 0 { 107 left.EarliestEpochMS = right.EarliestEpochMS 108 } 109 if right.LatestEpochMS != 0 && left.LatestEpochMS == 0 { 110 left.LatestEpochMS = right.LatestEpochMS 111 } 112 if right.SegbaseDir != "" && left.SegbaseDir == "" { 113 left.SegbaseDir = right.SegbaseDir 114 } 115 if len(right.ColumnNames) > 0 && len(left.ColumnNames) == 0 { 116 left.ColumnNames = right.ColumnNames 117 } 118 if right.NumBlocks > 0 && left.NumBlocks == 0 { 119 left.NumBlocks = right.NumBlocks 120 } 121 122 if right.MicroIndexSize != 0 && left.MicroIndexSize == 0 { 123 left.MicroIndexSize = right.MicroIndexSize 124 } 125 126 if right.SearchMetadataSize != 0 && left.SearchMetadataSize == 0 { 127 left.SearchMetadataSize = right.SearchMetadataSize 128 } 129 130 if right.RecordCount > 0 && left.RecordCount == 0 { 131 left.RecordCount = right.RecordCount 132 } 133 134 return left, nil 135 } 136 137 func RebalanceInMemoryCmi(metadataSizeBytes uint64) { 138 globalMetadata.rebalanceCmi(metadataSizeBytes) 139 } 140 141 // Entry point to rebalance what is loaded in memory depending on BLOCK_MICRO_MEM_SIZE 142 func (hm *allSegmentMetadata) rebalanceCmi(metadataSizeBytes uint64) { 143 144 sTime := time.Now() 145 146 hm.updateLock.RLock() 147 cmiIndex := hm.getCmiMaxIndicesToLoad(metadataSizeBytes) 148 evicted := hm.evictCmiPastIndices(cmiIndex) 149 inMemSize, inMemCMI, newloaded := hm.loadCmiUntilIndex(cmiIndex) 150 151 log.Infof("rebalanceCmi: CMI, inMem: %+v, allocated: %+v MB, evicted: %v, newloaded: %v, took: %vms", 152 inMemCMI, utils.ConvertUintBytesToMB(inMemSize), 153 evicted, newloaded, int(time.Since(sTime).Milliseconds())) 154 GlobalSegStoreSummary.SetInMemoryBlockMicroIndexCount(uint64(inMemCMI)) 155 GlobalSegStoreSummary.SetInMemoryBlockMicroIndexSizeMB(utils.ConvertUintBytesToMB(inMemSize)) 156 hm.updateLock.RUnlock() 157 } 158 159 /* 160 Returns the max indices that should have metadata loaded 161 162 First value is the max index where CMIs should be loaded 163 */ 164 func (hm *allSegmentMetadata) getCmiMaxIndicesToLoad(totalMem uint64) int { 165 // 1. get max index to load assuming both CMI & search metadata will be loaded 166 // 2. with remaining size, load whatever search metadata that fits in memory 167 168 numBlocks := len(hm.allSegmentMicroIndex) 169 totalMBAllocated := uint64(0) 170 maxCmiIndex := 0 171 for ; maxCmiIndex < numBlocks; maxCmiIndex++ { 172 cmiSize := hm.allSegmentMicroIndex[maxCmiIndex].MicroIndexSize + hm.allSegmentMicroIndex[maxCmiIndex].SearchMetadataSize 173 if cmiSize+totalMBAllocated > totalMem { 174 break 175 } 176 totalMBAllocated += cmiSize 177 } 178 179 return maxCmiIndex 180 } 181 182 func (hm *allSegmentMetadata) evictCmiPastIndices(cmiIndex int) int { 183 idxToClear := make([]int, 0) 184 for i := cmiIndex; i < len(hm.allSegmentMicroIndex); i++ { 185 if hm.allSegmentMicroIndex[i].loadedMicroIndices { 186 idxToClear = append(idxToClear, i) 187 } 188 } 189 190 if len(idxToClear) > 0 { 191 for _, idx := range idxToClear { 192 hm.allSegmentMicroIndex[idx].clearMicroIndices() 193 } 194 } 195 return len(idxToClear) 196 } 197 198 // Returns total in memory size in bytes, total cmis in memory, total search metadata in memory 199 func (hm *allSegmentMetadata) loadCmiUntilIndex(cmiIdx int) (uint64, int, int) { 200 201 totalSize := uint64(0) 202 totalCMICount := int(0) 203 204 idxToLoad := make([]int, 0) 205 206 for i := 0; i < cmiIdx; i++ { 207 if !hm.allSegmentMicroIndex[i].loadedMicroIndices { 208 idxToLoad = append(idxToLoad, i) 209 } else { 210 totalSize += hm.allSegmentMicroIndex[i].MicroIndexSize 211 totalCMICount += 1 212 } 213 } 214 215 if len(idxToLoad) > 0 { 216 a, b := hm.loadParallel(idxToLoad, true) 217 totalSize += a 218 totalCMICount += b 219 } 220 221 return totalSize, totalCMICount, len(idxToLoad) 222 } 223 224 /* 225 Parameters: 226 227 idxToLoad: indices in the allSegmentMicroIndex to be loaded 228 cmi: whether to load cmi (true) or ssm (false) 229 230 Returns: 231 232 totalSize: 233 totalEntities: 234 */ 235 func (hm *allSegmentMetadata) loadParallel(idxToLoad []int, cmi bool) (uint64, int) { 236 237 totalSize := uint64(0) 238 totalEntities := int(0) 239 240 wg := &sync.WaitGroup{} 241 var err error 242 var ssmBufs [][]byte 243 parallelism := int(config.GetParallelism()) 244 if !cmi { 245 ssmBufs = make([][]byte, parallelism) 246 for i := 0; i < parallelism; i++ { 247 ssmBufs[i] = make([]byte, 0) 248 } 249 } 250 251 for i, idx := range idxToLoad { 252 wg.Add(1) 253 go func(myIdx int, rbufIdx int) { 254 if cmi { 255 pqsCols, err := querytracker.GetPersistentColumns(hm.allSegmentMicroIndex[myIdx].VirtualTableName, hm.allSegmentMicroIndex[idx].OrgId) 256 if err != nil { 257 log.Errorf("loadParallel: error getting persistent columns: %v", err) 258 } else { 259 err = hm.allSegmentMicroIndex[myIdx].loadMicroIndices(map[uint16]map[string]bool{}, true, pqsCols, true) 260 if err != nil { 261 log.Errorf("loadParallel: failed to load SSM at index %d. Error %v", 262 myIdx, err) 263 } 264 } 265 } else { 266 ssmBufs[rbufIdx], err = hm.allSegmentMicroIndex[myIdx].LoadSearchMetadata(ssmBufs[rbufIdx]) 267 if err != nil { 268 log.Errorf("loadParallel: failed to load SSM at index %d. Error: %v", myIdx, err) 269 } 270 } 271 wg.Done() 272 }(idx, i%parallelism) 273 if i%parallelism == 0 { 274 wg.Wait() 275 } 276 } 277 wg.Wait() 278 279 for _, idx := range idxToLoad { 280 if cmi { 281 if hm.allSegmentMicroIndex[idx].loadedMicroIndices { 282 totalSize += hm.allSegmentMicroIndex[idx].MicroIndexSize 283 totalEntities += 1 284 } 285 } else { 286 if hm.allSegmentMicroIndex[idx].loadedSearchMetadata { 287 totalSize += hm.allSegmentMicroIndex[idx].SearchMetadataSize 288 totalEntities += 1 289 } 290 } 291 } 292 return totalSize, totalEntities 293 } 294 295 func (hm *allSegmentMetadata) deleteSegmentKey(key string) { 296 hm.updateLock.Lock() 297 defer hm.updateLock.Unlock() 298 hm.deleteSegmentKeyInternal(key) 299 } 300 301 func (hm *allSegmentMetadata) deleteTable(table string, orgid uint64) { 302 hm.updateLock.Lock() 303 defer hm.updateLock.Unlock() 304 305 tableSegments, ok := hm.tableSortedMetadata[table] 306 if !ok { 307 return 308 } 309 310 allSegKeysInTable := make(map[string]bool, len(tableSegments)) 311 for _, segment := range tableSegments { 312 if segment.OrgId == orgid { 313 allSegKeysInTable[segment.SegmentKey] = true 314 } 315 } 316 for segKey := range allSegKeysInTable { 317 hm.deleteSegmentKeyInternal(segKey) 318 } 319 delete(hm.tableSortedMetadata, table) 320 GlobalSegStoreSummary.DecrementTotalTableCount() 321 } 322 323 // internal function to delete segment key from all SiglensMetadata structs 324 // caller is responsible for acquiring locks 325 func (hm *allSegmentMetadata) deleteSegmentKeyInternal(key string) { 326 var tName string 327 for i, sMetadata := range hm.allSegmentMicroIndex { 328 if sMetadata.SegmentKey == key { 329 hm.allSegmentMicroIndex = append(hm.allSegmentMicroIndex[:i], hm.allSegmentMicroIndex[i+1:]...) 330 tName = sMetadata.VirtualTableName 331 break 332 } 333 } 334 delete(hm.segmentMetadataReverseIndex, key) 335 if tName == "" { 336 log.Debugf("DeleteSegmentKey key %+v was not found in metadata", key) 337 return 338 } 339 sortedTableSlice, ok := hm.tableSortedMetadata[tName] 340 if !ok { 341 return 342 } 343 344 for i, sMetadata := range sortedTableSlice { 345 if sMetadata.SegmentKey == key { 346 sortedTableSlice = append(sortedTableSlice[:i], sortedTableSlice[i+1:]...) 347 break 348 } 349 } 350 hm.tableSortedMetadata[tName] = sortedTableSlice 351 GlobalSegStoreSummary.DecrementTotalSegKeyCount() 352 353 } 354 355 func (hm *allSegmentMetadata) getMicroIndex(segKey string) (*SegmentMicroIndex, bool) { 356 blockMicroIndex, ok := hm.segmentMetadataReverseIndex[segKey] 357 return blockMicroIndex, ok 358 } 359 360 func DeleteSegmentKey(segKey string) { 361 globalMetadata.deleteSegmentKey(segKey) 362 } 363 364 func DeleteVirtualTable(vTable string, orgid uint64) { 365 globalMetadata.deleteTable(vTable, orgid) 366 } 367 368 /* 369 Internally, this will allocate 30% of the SSM size to metrics and the remaining to logs 370 */ 371 func RebalanceInMemorySsm(ssmSizeBytes uint64) { 372 logsSSM := uint64(float64(ssmSizeBytes) * 0.30) 373 metricsSSM := uint64(float64(ssmSizeBytes) * 0.70) 374 globalMetadata.rebalanceSsm(logsSSM) 375 globalMetricsMetadata.rebalanceMetricsSsm(metricsSSM) 376 } 377 378 func (hm *allSegmentMetadata) rebalanceSsm(ssmSizeBytes uint64) { 379 380 sTime := time.Now() 381 382 hm.updateLock.RLock() 383 searchIndex := hm.getSsmMaxIndicesToLoad(ssmSizeBytes) 384 evicted := hm.evictSsmPastIndices(searchIndex) 385 386 inMemSize, inMemSearchMetaCount, newloaded := hm.loadSsmUntilIndex(searchIndex) 387 388 log.Infof("rebalanceSsm SSM, inMem: %+v SSM, allocated: %+v MB, evicted: %v, newloaded: %v, took: %vms", 389 inMemSearchMetaCount, utils.ConvertUintBytesToMB(inMemSize), 390 evicted, newloaded, int(time.Since(sTime).Milliseconds())) 391 392 GlobalSegStoreSummary.SetInMemorySearchmetadataCount(uint64(inMemSearchMetaCount)) 393 GlobalSegStoreSummary.SetInMemorySsmSizeMB(utils.ConvertUintBytesToMB(inMemSize)) 394 hm.updateLock.RUnlock() 395 } 396 397 /* 398 Returns the max indices that should have SSM(segmentsearchmeta) loaded 399 */ 400 func (hm *allSegmentMetadata) getSsmMaxIndicesToLoad(totalMem uint64) int { 401 402 numBlocks := len(hm.allSegmentMicroIndex) 403 totalMBAllocated := uint64(0) 404 maxSearchIndex := 0 405 for ; maxSearchIndex < numBlocks; maxSearchIndex++ { 406 searchMetadataSize := hm.allSegmentMicroIndex[maxSearchIndex].SearchMetadataSize 407 if searchMetadataSize+totalMBAllocated > totalMem { 408 break 409 } 410 totalMBAllocated += searchMetadataSize 411 } 412 413 return maxSearchIndex 414 } 415 416 func (hm *allSegmentMetadata) evictSsmPastIndices(searchIndex int) int { 417 418 idxToClear := make([]int, 0) 419 for i := searchIndex; i < len(hm.allSegmentMicroIndex); i++ { 420 if hm.allSegmentMicroIndex[i].loadedSearchMetadata { 421 idxToClear = append(idxToClear, i) 422 } 423 } 424 425 if len(idxToClear) > 0 { 426 for _, idx := range idxToClear { 427 hm.allSegmentMicroIndex[idx].clearSearchMetadata() 428 } 429 } 430 return len(idxToClear) 431 } 432 433 // Returns total in memory size in bytes, total search metadata in memory 434 func (hm *allSegmentMetadata) loadSsmUntilIndex(searchMetaIdx int) (uint64, int, int) { 435 totalSize := uint64(0) 436 totalSearchMetaCount := int(0) 437 438 idxToLoad := make([]int, 0) 439 for i := 0; i < searchMetaIdx; i++ { 440 if !hm.allSegmentMicroIndex[i].loadedSearchMetadata { 441 idxToLoad = append(idxToLoad, i) 442 } else { 443 totalSize += hm.allSegmentMicroIndex[i].SearchMetadataSize 444 totalSearchMetaCount += 1 445 } 446 } 447 448 if len(idxToLoad) > 0 { 449 a, b := hm.loadParallel(idxToLoad, false) 450 totalSize += a 451 totalSearchMetaCount += b 452 } 453 454 return totalSize, totalSearchMetaCount, len(idxToLoad) 455 } 456 457 func GetTotalSMICount() int64 { 458 return int64(len(globalMetadata.allSegmentMicroIndex)) 459 } 460 461 func GetNumOfSearchedRecordsRotated(segKey string) uint64 { 462 globalMetadata.updateLock.RLock() 463 smi, segKeyOk := globalMetadata.segmentMetadataReverseIndex[segKey] 464 globalMetadata.updateLock.RUnlock() 465 if !segKeyOk { 466 return 0 467 } 468 return uint64(smi.RecordCount) 469 } 470 471 // returns the time range of the blocks in the segment that do not exist in spqmr 472 // if the timeRange is nil, no blocks were found in metadata that do not exist in spqmr 473 func GetTSRangeForMissingBlocks(segKey string, tRange *dtu.TimeRange, spqmr *pqmr.SegmentPQMRResults) *dtu.TimeRange { 474 globalMetadata.updateLock.RLock() 475 defer globalMetadata.updateLock.RUnlock() 476 sMicroIdx, ok := globalMetadata.segmentMetadataReverseIndex[segKey] 477 if !ok { 478 log.Errorf("SegKey %+v does not exist in metadata yet existed for pqs!", segKey) 479 return nil 480 } 481 482 if !sMicroIdx.loadedSearchMetadata { 483 _, err := sMicroIdx.LoadSearchMetadata([]byte{}) 484 if err != nil { 485 log.Errorf("Error loading search metadata: %+v", err) 486 return nil 487 } 488 defer sMicroIdx.clearSearchMetadata() 489 } 490 491 var fRange *dtu.TimeRange 492 for i, blockSummary := range sMicroIdx.BlockSummaries { 493 if tRange.CheckRangeOverLap(blockSummary.LowTs, blockSummary.HighTs) && !spqmr.DoesBlockExist(uint16(i)) { 494 495 if fRange == nil { 496 fRange = &dtu.TimeRange{} 497 fRange.StartEpochMs = blockSummary.LowTs 498 fRange.EndEpochMs = blockSummary.HighTs 499 } else { 500 if blockSummary.LowTs < fRange.StartEpochMs { 501 fRange.StartEpochMs = blockSummary.LowTs 502 } 503 if blockSummary.HighTs < fRange.EndEpochMs { 504 fRange.EndEpochMs = blockSummary.HighTs 505 } 506 } 507 } 508 } 509 return fRange 510 } 511 512 func IsUnrotatedQueryNeeded(timeRange *dtu.TimeRange, indexNames []string) bool { 513 globalMetadata.updateLock.RLock() 514 defer globalMetadata.updateLock.RUnlock() 515 for _, index := range indexNames { 516 if sortedTableMetadata, ok := globalMetadata.tableSortedMetadata[index]; !ok { 517 return true // if table doesn't exist in segstore, assume it exists in unrotated 518 } else { 519 // as this list is decreasing by LatestEpochMS, we only need to check index 0 520 if timeRange.EndEpochMs > sortedTableMetadata[0].LatestEpochMS { 521 return true 522 } 523 } 524 } 525 return false 526 } 527 528 /* 529 Returns: 530 1. map of tableName -> map segKey -> segKey time range 531 2. []string of segKeys that are in the time range. that exist in the map 532 2. final matched count 533 3. total possible count 534 */ 535 func FilterSegmentsByTime(timeRange *dtu.TimeRange, indexNames []string, orgid uint64) (map[string]map[string]*dtu.TimeRange, uint64, uint64) { 536 537 globalMetadata.updateLock.RLock() 538 defer globalMetadata.updateLock.RUnlock() 539 timePassed := uint64(0) 540 totalChecked := uint64(0) 541 retVal := make(map[string]map[string]*dtu.TimeRange) 542 for _, index := range indexNames { 543 tableMicroIndices, ok := globalMetadata.tableSortedMetadata[index] 544 if !ok { 545 continue 546 } 547 548 for _, smi := range tableMicroIndices { 549 if timeRange.CheckRangeOverLap(smi.EarliestEpochMS, smi.LatestEpochMS) && smi.OrgId == orgid { 550 if _, ok := retVal[index]; !ok { 551 retVal[index] = make(map[string]*dtu.TimeRange) 552 } 553 retVal[index][smi.SegmentKey] = &dtu.TimeRange{ 554 StartEpochMs: smi.EarliestEpochMS, 555 EndEpochMs: smi.LatestEpochMS, 556 } 557 timePassed++ 558 } 559 } 560 totalChecked += uint64(len(tableMicroIndices)) 561 } 562 return retVal, timePassed, totalChecked 563 } 564 565 // returns the a map with columns as keys and returns a bool if the segkey/table was found 566 func CheckAndGetColsForSegKey(segKey string, vtable string) (map[string]bool, bool) { 567 568 globalMetadata.updateLock.RLock() 569 segmentMetadata, segKeyOk := globalMetadata.segmentMetadataReverseIndex[segKey] 570 globalMetadata.updateLock.RUnlock() 571 if !segKeyOk { 572 return nil, false 573 } 574 575 return segmentMetadata.getColumns(), true 576 } 577 578 func DoesColumnExistForTable(cName string, indices []string) bool { 579 globalMetadata.updateLock.RLock() 580 defer globalMetadata.updateLock.RUnlock() 581 for _, index := range indices { 582 tableSMI, ok := globalMetadata.tableSortedMetadata[index] 583 if !ok { 584 continue 585 } 586 587 for _, smi := range tableSMI { 588 _, ok := smi.ColumnNames[cName] 589 if ok { 590 return true 591 } 592 } 593 } 594 return false 595 } 596 597 func GetAllColNames(indices []string) []string { 598 globalMetadata.updateLock.RLock() 599 defer globalMetadata.updateLock.RUnlock() 600 601 colNamesMap := make(map[string]bool) 602 for _, index := range indices { 603 tableSMI, ok := globalMetadata.tableSortedMetadata[index] 604 if !ok { 605 continue 606 } 607 for _, smi := range tableSMI { 608 for cName := range smi.ColumnNames { 609 colNamesMap[cName] = true 610 } 611 } 612 } 613 colNames := make([]string, 0, len(colNamesMap)) 614 for cName := range colNamesMap { 615 colNames = append(colNames, cName) 616 } 617 return colNames 618 }