github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/blob/local/localcleaner.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 local 18 19 import ( 20 "container/heap" 21 "fmt" 22 "sort" 23 "time" 24 25 "github.com/shirou/gopsutil/v3/disk" 26 "github.com/siglens/siglens/pkg/config" 27 "github.com/siglens/siglens/pkg/segment/structs" 28 "github.com/siglens/siglens/pkg/segment/utils" 29 log "github.com/sirupsen/logrus" 30 ) 31 32 func initLocalCleaner() { 33 // initialize sort 34 heap.Init(&allSortedSegSetFiles) 35 for segSetFile := range segSetKeys { 36 segSet := segSetKeys[segSetFile] 37 if segSetFile != "" { 38 allSortedSegSetFiles.Push(segSet) 39 } 40 } 41 42 go removeFilesForMemoryLoop() 43 44 } 45 46 func removeFilesForMemoryLoop() { 47 for { 48 freeMemNeeded := getMemToBeFreed() 49 log.Debugf("removeFilesForMemoryLoop: Free memory needed: %vMB", utils.ConvertUintBytesToMB(freeMemNeeded)) 50 if freeMemNeeded > 0 { 51 err := removeFilesForDiskSpace(freeMemNeeded) 52 if err != nil { 53 log.Errorf("removeFilesForMemoryLoop: Error making space: %v", err) 54 } 55 } 56 time.Sleep(time.Second * 30) 57 } 58 } 59 60 func getMemToBeFreed() uint64 { 61 s, err := disk.Usage(config.GetDataPath()) 62 if err != nil { 63 log.Errorf("getMemToBeFreed: Error getting disk usage for / err=%v", err) 64 return 0 65 } 66 allowedVolume := (s.Total * config.GetDataDiskThresholdPercent()) / 100 67 if s.Used < allowedVolume { 68 return 0 69 } 70 return s.Used - allowedVolume 71 } 72 73 /* 74 Gets all candidates for removal based on sizeToRemove 75 76 Canidates are all present and not in use SegSetData that together sum up at least sizeToRemove 77 If we have more elements after summing to sizeToRemove, add them to the canidate list for access time sorting 78 */ 79 func getCanidatesForRemoval(sizeToRemove uint64) []*structs.SegSetData { 80 81 segSetKeysLock.Lock() 82 defer segSetKeysLock.Unlock() 83 topNCandidates := make([]*structs.SegSetData, 0) 84 inUseFiles := make([]*structs.SegSetData, 0) 85 candidateSize := uint64(0) 86 idx := 0 87 numCandidates := allSortedSegSetFiles.Len() 88 for candidateSize < sizeToRemove && idx < numCandidates { 89 candidate := heap.Pop(&allSortedSegSetFiles).(*structs.SegSetData) 90 if !candidate.InUse { 91 candidateSize += candidate.Size 92 topNCandidates = append(topNCandidates, candidate) 93 } else { 94 inUseFiles = append(inUseFiles, candidate) 95 } 96 idx++ 97 } 98 // if we have filled up canidateSize with sizeToRemove and more elements exist in the heap, 99 // add them to the candidates so they can be sorted by access time 100 if idx < numCandidates { 101 if finalCount := idx * 2; finalCount < numCandidates { 102 for idx < finalCount && idx < numCandidates { 103 candidate := heap.Pop(&allSortedSegSetFiles).(*structs.SegSetData) 104 if !candidate.InUse { 105 topNCandidates = append(topNCandidates, candidate) 106 } else { 107 inUseFiles = append(inUseFiles, candidate) 108 } 109 idx++ 110 } 111 } 112 } 113 // re-add element to the heap 114 for _, inUse := range inUseFiles { 115 heap.Push(&allSortedSegSetFiles, inUse) 116 } 117 return topNCandidates 118 } 119 120 // Removes sizeToRemove bytes from disk to make space 121 func removeFilesForDiskSpace(sizeToRemove uint64) error { 122 recreateLocalHeap() 123 if allSortedSegSetFiles.Len() == 0 { 124 log.Infof("removeFilesForDiskSpace: No more segset to delete. Cannot make space. sizeToRemove=%v", sizeToRemove) 125 return fmt.Errorf("no more segset to delete. cannot make space") 126 } 127 log.Infof("removeFilesForDiskSpace: Data disk threshold reached. Removing %d bytes.", sizeToRemove) 128 memFreed := uint64(0) 129 topNCandidates := getCanidatesForRemoval(sizeToRemove) 130 131 sort.SliceStable(topNCandidates, func(i, j int) bool { 132 // whichever has been accessed the least, should be deleted first 133 return topNCandidates[i].AccessTime < topNCandidates[j].AccessTime 134 }) 135 136 removedCount := 0 137 failedToDelete := 0 138 log.Infof("removeFilesForDiskSpace: Found %d candidates for removal out of %d", len(topNCandidates), len(segSetKeys)) 139 for i := 0; i < len(topNCandidates) && memFreed < sizeToRemove; i++ { 140 candidate := topNCandidates[i] 141 memFreed += candidate.Size 142 segSetFile := candidate.SegSetFileName 143 err := DeleteLocal(segSetFile) 144 if err != nil { 145 log.Errorf("removeFilesForDiskSpace: Error deleting segSetFile %v Error: %v", segSetFile, err) 146 failedToDelete++ 147 memFreed -= candidate.Size 148 } else { 149 log.Debugf("removeFilesForDiskSpace: deleted segSetFile %v", segSetFile) 150 removedCount++ 151 } 152 } 153 log.Infof("removeFilesForDiskSpace: Successfully removed %+v MB of segSetFiles from disk across %d files. Failed to remove %d", 154 utils.ConvertUintBytesToMB(memFreed), removedCount, failedToDelete) 155 156 // recreate the heap with all elements in the map 157 recreateLocalHeap() 158 if memFreed == 0 { 159 return fmt.Errorf("failed to delete any memory to accommodate size of %+v", sizeToRemove) 160 } 161 162 return nil 163 } 164 165 func recreateLocalHeap() { 166 // recreate the heap with all elements in the map 167 segSetKeysLock.Lock() 168 defer segSetKeysLock.Unlock() 169 allSortedSegSetFiles = make([]*structs.SegSetData, 0) 170 heap.Init(&allSortedSegSetFiles) 171 for segSetFile, segSet := range segSetKeys { 172 if segSetFile == "" { 173 delete(segSetKeys, segSetFile) 174 continue 175 } 176 allSortedSegSetFiles.Push(segSet) 177 } 178 }