github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/segment/memory/limit/memorylimit.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 limit
    18  
    19  import (
    20  	"errors"
    21  	"sync/atomic"
    22  	"time"
    23  
    24  	"github.com/siglens/siglens/pkg/config"
    25  	"github.com/siglens/siglens/pkg/segment/memory"
    26  	"github.com/siglens/siglens/pkg/segment/query/metadata"
    27  	"github.com/siglens/siglens/pkg/segment/structs"
    28  	"github.com/siglens/siglens/pkg/segment/utils"
    29  	"github.com/siglens/siglens/pkg/segment/writer"
    30  	log "github.com/sirupsen/logrus"
    31  )
    32  
    33  const MINUTES_UPDATE_METADATA_MEM_ALLOC = 1
    34  
    35  var LOG_GLOBAL_MEM_FREQUENCY = 5
    36  
    37  func InitMemoryLimiter() {
    38  	totalAvailableSizeBytes := config.GetTotalMemoryAvailable()
    39  	log.Infof("InitQueryNode: Total available memory %+v MB", utils.ConvertUintBytesToMB(totalAvailableSizeBytes))
    40  
    41  	maxBlockMetaInMemory := uint64(0)
    42  	maxSearchAvailableSize := uint64(0)
    43  	maxBlockMicroRuntime := uint64(0)
    44  	maxSsmInMemory := uint64(0)
    45  	metricsInMemory := uint64(0)
    46  
    47  	maxSearchAvailableSize = uint64(float64(totalAvailableSizeBytes) * utils.RAW_SEARCH_MEM_PERCENT / 100)
    48  	maxBlockMicroRuntime = uint64(float64(totalAvailableSizeBytes) * utils.MICRO_IDX_CHECK_MEM_PERCENT / 100)
    49  	maxBlockMetaInMemory = uint64(float64(totalAvailableSizeBytes) * utils.MICRO_IDX_MEM_PERCENT / 100)
    50  	maxSsmInMemory = uint64(float64(totalAvailableSizeBytes) * utils.SSM_MEM_PERCENT / 100)
    51  	metricsInMemory = uint64(float64(totalAvailableSizeBytes) * utils.METRICS_MEMORY_MEM_PERCENT / 100)
    52  
    53  	if config.IsDebugMode() {
    54  		LOG_GLOBAL_MEM_FREQUENCY = 1
    55  	}
    56  
    57  	// Total available memory should not include block runtime so rebalancing is still accurate
    58  	totalAvailableSizeBytes = totalAvailableSizeBytes - maxBlockMicroRuntime
    59  	memory.GlobalMemoryTracker = &structs.MemoryTracker{
    60  		TotalAllocatableBytes: totalAvailableSizeBytes,
    61  
    62  		CmiInMemoryAllocatedBytes: maxBlockMetaInMemory,
    63  		CmiRuntimeAllocatedBytes:  maxBlockMicroRuntime,
    64  
    65  		SegSearchRequestedBytes: maxSearchAvailableSize,
    66  
    67  		SegWriterUsageBytes:       0,
    68  		SegStoreSummary:           metadata.GlobalSegStoreSummary,
    69  		SsmInMemoryAllocatedBytes: maxSsmInMemory,
    70  
    71  		MetricsSegmentMaxSize: metricsInMemory,
    72  	}
    73  
    74  	metadata.InitBlockMetaCheckLimiter(int64(maxBlockMicroRuntime))
    75  	go rebalanceMemoryAllocationLoop()
    76  }
    77  
    78  func printMemoryManagerSummary() {
    79  	numLoadedUnrotated, totalUnrotated := writer.GetUnrotatedMetadataInfo()
    80  	unrotaedSize := writer.GetSizeOfUnrotatedMetadata()
    81  	log.Infof("GlobalMemoryTracker: Total amount of memory available is %+v MB", utils.ConvertUintBytesToMB(memory.GlobalMemoryTracker.TotalAllocatableBytes))
    82  	log.Infof("GlobalMemoryTracker: AllSegReadStores has %v total segment files across %v tables. Microindices have been allocated %+v MB",
    83  		memory.GlobalMemoryTracker.SegStoreSummary.TotalSegmentCount, memory.GlobalMemoryTracker.SegStoreSummary.TotalTableCount, utils.ConvertUintBytesToMB(memory.GlobalMemoryTracker.CmiInMemoryAllocatedBytes))
    84  
    85  	log.Infof("GlobalMemoryTracker: AllSegReadStores has %v CMI entries in memory. This accounts for %v MB",
    86  		memory.GlobalMemoryTracker.SegStoreSummary.InMemoryCMICount,
    87  		memory.GlobalMemoryTracker.SegStoreSummary.InMemoryBlockMicroIndexSizeMB)
    88  
    89  	log.Infof("GlobalMemoryTracker: AllSegReadStores %v SSM entries in memory. This accounts for %v MB",
    90  		memory.GlobalMemoryTracker.SegStoreSummary.InMemorySearchMetadataCount,
    91  		memory.GlobalMemoryTracker.SegStoreSummary.InMemorySsmSizeMB)
    92  
    93  	log.Infof("GlobalMemoryTracker: MetricsMetadata has %v segments in memory. Out of which %v segment have SSMs loaded. This accounts for %v MB",
    94  		memory.GlobalMemoryTracker.SegStoreSummary.TotalMetricsSegmentCount,
    95  		memory.GlobalMemoryTracker.SegStoreSummary.InMemoryMetricsSearchMetadataCount,
    96  		memory.GlobalMemoryTracker.SegStoreSummary.InMemoryMetricsBSumSizeMB)
    97  
    98  	log.Infof("GlobalMemoryTracker: Unrotated metadata has %v total segKeys. %+v have loaded metadata in memory. This accounts for %v MB",
    99  		totalUnrotated, numLoadedUnrotated, utils.ConvertUintBytesToMB(unrotaedSize))
   100  	log.Infof("GlobalMemoryTracker: SegSearch has been allocated %v MB.", utils.ConvertUintBytesToMB(memory.GlobalMemoryTracker.SegSearchRequestedBytes))
   101  	log.Infof("GlobalMemoryTracker: SegWriter has been allocated %v MB. MetricsWriter has been allocated %v MB.",
   102  		utils.ConvertUintBytesToMB(memory.GlobalMemoryTracker.SegWriterUsageBytes), utils.ConvertUintBytesToMB(memory.GetAvailableMetricsIngestMemory()))
   103  }
   104  
   105  func rebalanceMemoryAllocationLoop() {
   106  	count := 0
   107  	for {
   108  		rebalanceMemoryAllocation()
   109  		if count%LOG_GLOBAL_MEM_FREQUENCY == 0 {
   110  			printMemoryManagerSummary()
   111  		}
   112  		count++
   113  		count = count % LOG_GLOBAL_MEM_FREQUENCY
   114  		time.Sleep(MINUTES_UPDATE_METADATA_MEM_ALLOC * time.Minute)
   115  	}
   116  }
   117  
   118  /*
   119  Main function that rebalances all memory limits with the following logic
   120  
   121  1. Get memory that we can allocate / move around
   122    - memoryAvailable = TotalAvailableBytes - size of writer segstores
   123  
   124  2. Allocate memory for segsearch. This will be the max memory of any single segment raw search we have seen
   125  3. From available memory, use percentages to get max size of metadata
   126  4. First, use as much metadata size as possible for unrotated data
   127    - if unrotated data is bigger than max metadata size, then use all metadata memory for unrotated data
   128    - when we remove unrotated data, we currently have no way to add it back so we will raw search the entire file
   129  
   130  5. After allocating for unrotated data, use remaining metadata size for rotated data
   131    - set global var & rebalance in metadata package
   132  */
   133  func rebalanceMemoryAllocation() {
   134  	rawWriterSize := writer.GetInMemorySize()
   135  	var memoryAvailable uint64
   136  	if rawWriterSize > memory.GlobalMemoryTracker.TotalAllocatableBytes {
   137  		memoryAvailable = 0
   138  	} else {
   139  		memoryAvailable = memory.GlobalMemoryTracker.TotalAllocatableBytes - rawWriterSize
   140  	}
   141  	if memoryAvailable < memory.GlobalMemoryTracker.CmiRuntimeAllocatedBytes {
   142  		memoryAvailable = 0
   143  	} else {
   144  		memoryAvailable = memoryAvailable - memory.GlobalMemoryTracker.CmiRuntimeAllocatedBytes
   145  	}
   146  
   147  	totalSsmMemory := uint64(float64(memoryAvailable) * utils.SSM_MEM_PERCENT / 100)
   148  	metadata.RebalanceInMemorySsm(totalSsmMemory)
   149  
   150  	if memory.GlobalMemoryTracker.SegSearchRequestedBytes > memoryAvailable {
   151  		memoryAvailable = 0
   152  		memory.GlobalMemoryTracker.SegSearchRequestedBytes = memoryAvailable
   153  	} else {
   154  		memoryAvailable = memoryAvailable - memory.GlobalMemoryTracker.SegSearchRequestedBytes
   155  	}
   156  
   157  	totalMetadataMemory := uint64(float64(memoryAvailable) * utils.MICRO_IDX_MEM_PERCENT / 100)
   158  	unrotatedMetadataMemory := writer.GetSizeOfUnrotatedMetadata()
   159  	if unrotatedMetadataMemory >= totalMetadataMemory {
   160  		unrotatedMetadataMemory = writer.RebalanceUnrotatedMetadata(totalMetadataMemory)
   161  	}
   162  
   163  	var blockMetadataMemory uint64
   164  	if unrotatedMetadataMemory > totalMetadataMemory {
   165  		blockMetadataMemory = 0
   166  	} else {
   167  		blockMetadataMemory = totalMetadataMemory - unrotatedMetadataMemory
   168  	}
   169  
   170  	metadata.RebalanceInMemoryCmi(blockMetadataMemory)
   171  	memory.GlobalMemoryTracker.CmiInMemoryAllocatedBytes = blockMetadataMemory
   172  	memory.GlobalMemoryTracker.SegWriterUsageBytes = rawWriterSize
   173  }
   174  
   175  // Creates space for search by removing cmi if needed. Returns error if no space can be found.
   176  // This function assumes only one segment is run at a time, and will not verify if >1 segment are run in parallel
   177  func RequestSearchMemory(sLimit uint64) error {
   178  
   179  	if sLimit <= memory.GlobalMemoryTracker.SegSearchRequestedBytes {
   180  		return nil
   181  	}
   182  	atomic.StoreUint64(&memory.GlobalMemoryTracker.SegSearchRequestedBytes, sLimit)
   183  	rebalanceMemoryAllocation()
   184  
   185  	if sLimit <= memory.GlobalMemoryTracker.SegSearchRequestedBytes {
   186  		return nil
   187  	}
   188  	// If try to rebalance and SegSearchAllocatedBytes did not change, then we could not allocate what was requested
   189  	log.Infof("Unable to allocate memory for segsearch! Current breakdown:")
   190  	printMemoryManagerSummary()
   191  	return errors.New("failed to allocate resources for segment search")
   192  }