From b17fde882860b10d8d59a24847ef93c539ec8c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D1=80=D0=B8=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=A1?= =?UTF-8?q?=D0=B0=D1=84=D1=80=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Fri, 12 Jun 2026 16:22:02 +0000 Subject: [PATCH] Upload files to "internal/storage" --- internal/storage/runtime_limit.go | 215 ++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 internal/storage/runtime_limit.go diff --git a/internal/storage/runtime_limit.go b/internal/storage/runtime_limit.go new file mode 100644 index 0000000..a808911 --- /dev/null +++ b/internal/storage/runtime_limit.go @@ -0,0 +1,215 @@ +/* + * Copyright 2026 Safronov Grigorii + * + * Licensed under the CDDL, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * https://opensource.org/licenses/CDDL-1.0 + */ + +// Файл: internal/storage/runtime_limits.go +// Назначение: Ограничения на размер коллекции/документа в рантайме + +package storage + +import ( + "fmt" + "sync" + "sync/atomic" + "time" +) + +// RuntimeLimitsManager управляет runtime-ограничениями +type RuntimeLimitsManager struct { + mu sync.RWMutex + globalMaxDocSize int64 // Максимальный размер документа (байт) + globalMaxCollSize int64 // Максимальный размер коллекции (байт) + globalMaxDocsPerColl int64 // Максимальное количество документов в коллекции + collectionOverrides map[string]*CollectionLimits // Переопределения для коллекций + metrics *LimitMetrics + logger LoggerInterface + enabled bool +} + +// CollectionLimits содержит лимиты для конкретной коллекции +type CollectionLimits struct { + MaxDocSize int64 + MaxCollectionSize int64 + MaxDocuments int64 + LastUpdated int64 +} + +// LimitMetrics хранит метрики ограничений +type LimitMetrics struct { + RejectedBySize atomic.Uint64 + RejectedByDocCount atomic.Uint64 + RejectedByCollSize atomic.Uint64 + LastCheckTime atomic.Int64 +} + +// RuntimeLimitsConfig содержит конфигурацию ограничений +type RuntimeLimitsConfig struct { + Enabled bool `json:"enabled"` + GlobalMaxDocSizeMB int `json:"global_max_doc_size_mb"` + GlobalMaxCollSizeMB int64 `json:"global_max_coll_size_mb"` + GlobalMaxDocsPerColl int64 `json:"global_max_docs_per_coll"` +} + +// DefaultRuntimeLimitsConfig возвращает конфигурацию по умолчанию +func DefaultRuntimeLimitsConfig() *RuntimeLimitsConfig { + return &RuntimeLimitsConfig{ + Enabled: true, + GlobalMaxDocSizeMB: 16, // 16 MB на документ + GlobalMaxCollSizeMB: 10240, // 10 GB на коллекцию + GlobalMaxDocsPerColl: 10000000, // 10 млн документов + } +} + +// NewRuntimeLimitsManager создаёт новый менеджер ограничений +func NewRuntimeLimitsManager(cfg *RuntimeLimitsConfig, logger LoggerInterface) *RuntimeLimitsManager { + if cfg == nil { + cfg = DefaultRuntimeLimitsConfig() + } + + rlm := &RuntimeLimitsManager{ + globalMaxDocSize: int64(cfg.GlobalMaxDocSizeMB) * 1024 * 1024, + globalMaxCollSize: cfg.GlobalMaxCollSizeMB * 1024 * 1024, + globalMaxDocsPerColl: cfg.GlobalMaxDocsPerColl, + collectionOverrides: make(map[string]*CollectionLimits), + metrics: &LimitMetrics{}, + logger: logger, + enabled: cfg.Enabled, + } + + if logger != nil { + logger.Debug(fmt.Sprintf("Runtime limits manager initialized: maxDoc=%dMB, maxColl=%dMB, maxDocs=%d", + cfg.GlobalMaxDocSizeMB, cfg.GlobalMaxCollSizeMB, cfg.GlobalMaxDocsPerColl)) + } + + return rlm +} + +// ValidateDocumentSize проверяет размер документа +func (rlm *RuntimeLimitsManager) ValidateDocumentSize(dbName, collName string, docSize int64) error { + if !rlm.enabled { + return nil + } + + // Проверяем переопределение для коллекции + limit := rlm.getCollectionLimit(dbName, collName) + + maxSize := rlm.globalMaxDocSize + if limit != nil && limit.MaxDocSize > 0 { + maxSize = limit.MaxDocSize + } + + if docSize > maxSize { + rlm.metrics.RejectedBySize.Add(1) + return fmt.Errorf("document size %d bytes exceeds limit %d bytes", docSize, maxSize) + } + + return nil +} + +// ValidateCollectionSize проверяет размер коллекции +func (rlm *RuntimeLimitsManager) ValidateCollectionSize(coll *Collection, newDocSize int64) error { + if !rlm.enabled { + return nil + } + + limit := rlm.getCollectionLimit(coll.DBName(), coll.Name()) + + maxSize := rlm.globalMaxCollSize + if limit != nil && limit.MaxCollectionSize > 0 { + maxSize = limit.MaxCollectionSize + } + + currentSize := coll.Size() + if currentSize+newDocSize > maxSize { + rlm.metrics.RejectedByCollSize.Add(1) + return fmt.Errorf("collection size would exceed limit %d bytes (current: %d, new: %d)", + maxSize, currentSize, newDocSize) + } + + return nil +} + +// ValidateDocumentCount проверяет количество документов в коллекции +func (rlm *RuntimeLimitsManager) ValidateDocumentCount(coll *Collection) error { + if !rlm.enabled { + return nil + } + + limit := rlm.getCollectionLimit(coll.DBName(), coll.Name()) + + maxDocs := rlm.globalMaxDocsPerColl + if limit != nil && limit.MaxDocuments > 0 { + maxDocs = limit.MaxDocuments + } + + currentCount := coll.Count() + if currentCount >= maxDocs { + rlm.metrics.RejectedByDocCount.Add(1) + return fmt.Errorf("collection has reached maximum document count %d", maxDocs) + } + + return nil +} + +// getCollectionLimit возвращает лимиты для коллекции +func (rlm *RuntimeLimitsManager) getCollectionLimit(dbName, collName string) *CollectionLimits { + rlm.mu.RLock() + defer rlm.mu.RUnlock() + + key := fmt.Sprintf("%s.%s", dbName, collName) + if limits, ok := rlm.collectionOverrides[key]; ok { + return limits + } + return nil +} + +// SetCollectionLimits устанавливает лимиты для коллекции +func (rlm *RuntimeLimitsManager) SetCollectionLimits(dbName, collName string, maxDocSizeMB int, maxCollSizeMB int64, maxDocuments int64) { + rlm.mu.Lock() + defer rlm.mu.Unlock() + + key := fmt.Sprintf("%s.%s", dbName, collName) + rlm.collectionOverrides[key] = &CollectionLimits{ + MaxDocSize: int64(maxDocSizeMB) * 1024 * 1024, + MaxCollectionSize: maxCollSizeMB * 1024 * 1024, + MaxDocuments: maxDocuments, + LastUpdated: time.Now().UnixMilli(), + } + + if rlm.logger != nil { + rlm.logger.Info(fmt.Sprintf("Set limits for %s: maxDoc=%dMB, maxColl=%dMB, maxDocs=%d", + key, maxDocSizeMB, maxCollSizeMB, maxDocuments)) + } +} + +// RemoveCollectionLimits удаляет переопределения для коллекции +func (rlm *RuntimeLimitsManager) RemoveCollectionLimits(dbName, collName string) { + rlm.mu.Lock() + defer rlm.mu.Unlock() + + key := fmt.Sprintf("%s.%s", dbName, collName) + delete(rlm.collectionOverrides, key) + + if rlm.logger != nil { + rlm.logger.Info(fmt.Sprintf("Removed limits override for %s", key)) + } +} + +// GetMetrics возвращает метрики +func (rlm *RuntimeLimitsManager) GetMetrics() map[string]interface{} { + return map[string]interface{}{ + "rejected_by_size": rlm.metrics.RejectedBySize.Load(), + "rejected_by_doc_count": rlm.metrics.RejectedByDocCount.Load(), + "rejected_by_coll_size": rlm.metrics.RejectedByCollSize.Load(), + "global_max_doc_size_mb": rlm.globalMaxDocSize / (1024 * 1024), + "global_max_coll_size_mb": rlm.globalMaxCollSize / (1024 * 1024), + "global_max_docs_per_coll": rlm.globalMaxDocsPerColl, + "enabled": rlm.enabled, + } +}