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  }