github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/retention/retention.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 retention 18 19 import ( 20 "math" 21 "path" 22 "sort" 23 "time" 24 25 "github.com/siglens/siglens/pkg/blob" 26 "github.com/siglens/siglens/pkg/common/fileutils" 27 "github.com/siglens/siglens/pkg/config" 28 "github.com/siglens/siglens/pkg/hooks" 29 "github.com/siglens/siglens/pkg/segment/query/metadata" 30 pqsmeta "github.com/siglens/siglens/pkg/segment/query/pqs/meta" 31 "github.com/siglens/siglens/pkg/segment/structs" 32 "github.com/siglens/siglens/pkg/segment/writer" 33 mmeta "github.com/siglens/siglens/pkg/segment/writer/metrics/meta" 34 log "github.com/sirupsen/logrus" 35 ) 36 37 const MAXIMUM_WARNINGS_COUNT = 5 38 39 // Starting the periodic retention based deletion 40 func InitRetentionCleaner() error { 41 if hook := hooks.GlobalHooks.ExtraRetentionCleanerHook; hook != nil { 42 err := hook() 43 if err != nil { 44 return err 45 } 46 } 47 48 go internalRetentionCleaner() 49 return nil 50 } 51 52 func internalRetentionCleaner() { 53 time.Sleep(1 * time.Minute) // sleep for 1min for the rest of the system to come up 54 55 var hook1Result string 56 if hook := hooks.GlobalHooks.InternalRetentionCleanerHook1; hook != nil { 57 hook1Result = hook() 58 } 59 60 deletionWarningCounter := 0 61 for { 62 if hook := hooks.GlobalHooks.InternalRetentionCleanerHook2; hook != nil { 63 hook(hook1Result, deletionWarningCounter) 64 } else { 65 DoRetentionBasedDeletion(config.GetCurrentNodeIngestDir(), config.GetRetentionHours(), 0) 66 } 67 68 deletionWarningCounter++ 69 time.Sleep(1 * time.Hour) 70 } 71 } 72 73 func DoRetentionBasedDeletion(ingestNodeDir string, retentionHours int, orgid uint64) { 74 currTime := time.Now() 75 deleteBefore := GetRetentionTimeMs(retentionHours, currTime) 76 77 currentSegmeta := path.Join(ingestNodeDir, writer.SegmetaSuffix) 78 allSegMetas, err := writer.ReadSegmeta(currentSegmeta) 79 if err != nil { 80 log.Errorf("doVolumeBasedDeletion: Failed to read segmeta, err: %v", err) 81 return 82 } 83 84 // Read metrics meta entries 85 currentMetricsMeta := path.Join(ingestNodeDir, mmeta.MetricsMetaSuffix) 86 87 allMetricMetas, err := mmeta.ReadMetricsMeta(currentMetricsMeta) 88 if err != nil { 89 log.Errorf("doVolumeBasedDeletion: Failed to get all metric meta entries, err: %v", err) 90 return 91 } 92 93 // Combine metrics and segments 94 allEntries := make([]interface{}, 0, len(allMetricMetas)+len(allSegMetas)) 95 for i := range allMetricMetas { 96 if allMetricMetas[i].OrgId == orgid { 97 allEntries = append(allEntries, allMetricMetas[i]) 98 } 99 } 100 for i := range allSegMetas { 101 if allSegMetas[i].OrgId == orgid { 102 allEntries = append(allEntries, allSegMetas[i]) 103 } 104 } 105 106 // Sort all entries based on latest epoch time 107 sort.Slice(allEntries, func(i, j int) bool { 108 var timeI uint64 109 if segMeta, ok := allEntries[i].(*structs.SegMeta); ok { 110 timeI = segMeta.LatestEpochMS 111 } else if metricMeta, ok := allEntries[i].(*structs.MetricsMeta); ok { 112 timeI = uint64(metricMeta.LatestEpochSec) * 1000 // convert to milliseconds 113 } else { 114 return false 115 } 116 117 var timeJ uint64 118 if segMeta, ok := allEntries[j].(*structs.SegMeta); ok { 119 timeJ = segMeta.LatestEpochMS 120 } else if metricMeta, ok := allEntries[j].(*structs.MetricsMeta); ok { 121 timeJ = uint64(metricMeta.LatestEpochSec) * 1000 // convert to milliseconds 122 } else { 123 return false 124 } 125 return timeI < timeJ 126 }) 127 128 segmentsToDelete := make(map[string]*structs.SegMeta) 129 metricSegmentsToDelete := make(map[string]*structs.MetricsMeta) 130 131 oldest := uint64(math.MaxUint64) 132 for _, metaEntry := range allEntries { 133 switch entry := metaEntry.(type) { 134 case *structs.MetricsMeta: 135 if uint64(entry.LatestEpochSec)*1000 <= deleteBefore { 136 metricSegmentsToDelete[entry.MSegmentDir] = entry 137 } 138 if oldest > uint64(entry.LatestEpochSec)*1000 { 139 oldest = uint64(entry.LatestEpochSec) * 1000 140 } 141 case *structs.SegMeta: 142 if entry.LatestEpochMS <= deleteBefore { 143 segmentsToDelete[entry.SegmentKey] = entry 144 } 145 if oldest > entry.LatestEpochMS { 146 oldest = entry.LatestEpochMS 147 } 148 } 149 } 150 151 log.Infof("doRetentionBasedDeletion: totalsegs=%v, segmentsToDelete=%v, metricsSegmentsToDelete=%v, oldest=%v, orgid=%v", 152 len(allEntries), len(segmentsToDelete), len(metricSegmentsToDelete), oldest, orgid) 153 154 // Delete all segment data 155 DeleteSegmentData(currentSegmeta, segmentsToDelete, true) 156 DeleteMetricsSegmentData(currentMetricsMeta, metricSegmentsToDelete, true) 157 } 158 159 func GetRetentionTimeMs(retentionHours int, currTime time.Time) uint64 { 160 retDur := time.Duration(retentionHours) * time.Hour 161 retentionTime := currTime.Add(-retDur) 162 return uint64(retentionTime.UnixMilli()) 163 } 164 func deleteSegmentsFromEmptyPqMetaFiles(segmentsToDelete map[string]*structs.SegMeta) { 165 for _, segmetaEntry := range segmentsToDelete { 166 for pqid := range segmetaEntry.AllPQIDs { 167 pqsmeta.DeleteSegmentFromPqid(pqid, segmetaEntry.SegmentKey) 168 } 169 } 170 } 171 172 func DeleteSegmentData(segmetaFile string, segmentsToDelete map[string]*structs.SegMeta, updateBlob bool) { 173 174 if len(segmentsToDelete) == 0 { 175 return 176 } 177 deleteSegmentsFromEmptyPqMetaFiles(segmentsToDelete) 178 // Delete segment key from all SiglensMetadata structs 179 for _, segMetaEntry := range segmentsToDelete { 180 metadata.DeleteSegmentKey(segMetaEntry.SegmentKey) 181 182 // Delete segment files from s3 183 dirPath := segMetaEntry.SegmentKey 184 filesToDelete := fileutils.GetAllFilesInDirectory(path.Dir(dirPath) + "/") 185 186 for _, file := range filesToDelete { 187 err := blob.DeleteBlob(file) 188 if err != nil { 189 log.Infof("deleteSegmentData: Error in deleting segment file %v in s3", file) 190 continue 191 } 192 } 193 } 194 195 writer.RemoveSegments(segmetaFile, segmentsToDelete) 196 197 // Upload the latest ingest nodes dir to s3 only if updateBlob is true 198 if !updateBlob { 199 return 200 } 201 err := blob.UploadIngestNodeDir() 202 if err != nil { 203 log.Errorf("deleteSegmentData: failed to upload ingestnodes dir to s3 err=%v", err) 204 return 205 } 206 } 207 208 func DeleteMetricsSegmentData(mmetaFile string, metricSegmentsToDelete map[string]*structs.MetricsMeta, updateBlob bool) { 209 if len(metricSegmentsToDelete) == 0 { 210 return 211 } 212 213 // Delete segment key from all SiglensMetadata structs 214 for _, metricsSegmentMeta := range metricSegmentsToDelete { 215 err := metadata.DeleteMetricsSegmentKey(metricsSegmentMeta.MSegmentDir) 216 if err != nil { 217 log.Errorf("deleteMetricsSegmentData: failed to delete metrics segment. Error:%v", err) 218 return 219 } 220 221 // Delete segment files from s3 222 dirPath := metricsSegmentMeta.MSegmentDir 223 filesToDelete := fileutils.GetAllFilesInDirectory(path.Dir(dirPath) + "/") 224 225 for _, file := range filesToDelete { 226 err := blob.DeleteBlob(file) 227 if err != nil { 228 log.Infof("deleteMetricsSegmentData: Error in deleting segment file %v in s3", file) 229 continue 230 } 231 } 232 } 233 234 mmeta.RemoveMetricsSegments(mmetaFile, metricSegmentsToDelete) 235 236 // Upload the latest ingest nodes dir to s3 only if updateBlob is true 237 if !updateBlob { 238 return 239 } 240 err := blob.UploadIngestNodeDir() 241 if err != nil { 242 log.Errorf("deleteMetricsSegmentData: failed to upload ingestnodes dir to s3 err=%v", err) 243 return 244 } 245 }