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  }