2026-09-20 21:50:21 +00:00
/*
* 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/config/config.go
// Назначение: Загрузка и парсинг TOML-конфигурации, валидация параметров,
// предоставление доступа к настройкам кластера, хранилища и REPL.
//
// Данный пакет отвечает за:
// - Чтение конфигурационного файла в формате TOML
// - Установку значений по умолчанию для всех параметров
// - Валидацию корректности настроек
// - Предоставление типобезопасного доступа к конфигурации через методы-геттеры
2026-10-05 21:26:41 +00:00
// - Сохранение конфигурации в TOML (SaveConfig) — используется REPL-командой
// "config set" в fallback-режиме, когда Raft недоступен.
2026-09-20 21:50:21 +00:00
//
// ИСПРАВЛЕНО: добавлена секция [metrics] (MetricsConfig) — иначе
// cmd/futriis/main.go не компилируется из-за cfg.Metrics.
2026-09-21 22:10:41 +00:00
//
// ИСПРАВЛЕНО (аудит, кросс-платформенность Linux/OpenIndiana):
2026-10-05 21:26:41 +00:00
// - BackupConfig дополнен полями IncludeSagaState и EnableIncremental.
2026-09-21 22:10:41 +00:00
// - Добавлены геттеры IsIncludeSagaStateEnabled / IsIncrementalEnabled.
// - В LoadConfig добавлены значения по умолчанию для новых полей.
// - В ValidateConfigFull добавлена валидация [backup] и [engines].
2026-10-05 21:26:41 +00:00
//
// ДОБАВЛЕНО (2026-10):
// - SaveConfig — атомарная запись конфигурации в TOML (tempfile + fsync + rename).
// - fsyncDir — синхронизация директории (no-op на Windows).
2026-09-20 21:50:21 +00:00
package config
import (
"fmt"
"net"
"os"
2026-10-05 21:26:41 +00:00
"path/filepath"
2026-09-20 21:50:21 +00:00
"strings"
"time"
"github.com/BurntSushi/toml"
)
// =============================================================================
// КОНСТАНТЫ ДЛЯ ВАЛИДАЦИИ
// =============================================================================
const (
// Максимальный размер файла конфигурации (1 MB)
maxConfigFileSize = 1 * 1024 * 1024
// Минимальная длина API-ключа
minAPIKeyLength = 8
)
// allowedLogLevels — допустимые уровни логирования
var allowedLogLevels = map [ string ] bool {
"debug" : true ,
"info" : true ,
"warn" : true ,
"error" : true ,
"fatal" : true ,
}
// allowedMigrationModes — допустимые режимы миграции
var allowedMigrationModes = map [ string ] bool {
"manual" : true ,
"semi_auto" : true ,
"auto" : true ,
}
// allowedCompressionAlgorithms — допустимые алгоритмы сжатия
var allowedCompressionAlgorithms = map [ string ] bool {
"snappy" : true ,
"lz4" : true ,
"zstd" : true ,
}
// =============================================================================
// ОСНОВНАЯ СТРУКТУРА КОНФИГУРАЦИИ
// =============================================================================
// Config представляет собой корневую структуру конфигурации,
// объединяющую все подсистемы приложения.
// Каждое поле соответствует секции в TOML-файле.
type Config struct {
Cluster ClusterConfig `toml:"cluster"` // Настройки кластеризации (Raft, узлы)
Storage StorageConfig `toml:"storage"` // Настройки хранилища (движки, ограничения)
Repl ReplConfig `toml:"repl"` // Настройки REPL (интерактивной оболочки)
Log LogConfig `toml:"log"` // Настройки логирования
API APIConfig `toml:"api"` // Настройки HTTP API
Replication ReplicationConfig `toml:"replication"` // Настройки репликации
Plugins PluginsConfig `toml:"plugins"` // Настройки системы плагинов (Lua)
Compression CompressionConfig `toml:"compression"` // Настройки сжатия данных
Performance PerformanceConfig `toml:"performance"` // Настройки производительности
Security SecurityConfig `toml:"security"` // Настройки безопасности (TLS)
Monitoring MonitoringConfig `toml:"monitoring"` // Настройки мониторинга (метрики, трассировка)
Recovery RecoveryConfig `toml:"recovery"` // Настройки восстановления после сбоев
WAL WALConfig `toml:"wal"` // Настройки WAL (Write-Ahead Log)
2026-10-05 21:26:41 +00:00
MVCC MVCCConfig `toml:"mvcc"` // Настройки MVCC
2026-09-20 21:50:21 +00:00
Transactions TransactionsConfig `toml:"transactions"` // Настройки транзакций
Saga SagaConfig `toml:"saga"` // Настройки SAGA оркестратора
2026-10-05 21:26:41 +00:00
ACL ACLConfig `toml:"acl"` // Настройки ACL
2026-09-20 21:50:21 +00:00
ClusterTLS TLSConfig `toml:"cluster_tls"` // Настройки TLS для кластерного взаимодействия
Backpressure BackpressureConfig `toml:"backpressure"` // Настройки механизма обратного давления
RuntimeLimits RuntimeLimitsConfig `toml:"runtime_limits"` // Ограничения времени выполнения
Autoscaling AutoscalingConfig `toml:"autoscaling"` // Настройки автоматического масштабирования
SchemaMigration SchemaMigrationConfig `toml:"schema_migration"` // Настройки миграции схемы
Backup BackupConfig `toml:"backup"` // Настройки резервного копирования
Engines EnginesConfig `toml:"engines"` // Настройки движков хранения
Migration MigrationConfig `toml:"migration"` // Настройки кросс-датацентровой миграции
2026-10-05 21:26:41 +00:00
Metrics MetricsConfig `toml:"metrics"` // Настройки Prometheus/Grafana
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ МЕТРИК
2026-09-20 21:50:21 +00:00
// =============================================================================
// MetricsConfig содержит настройки экспорта метрик в формате Prometheus.
type MetricsConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
CollectIntervalSec int `toml:"collect_interval_sec"`
2026-09-20 21:50:21 +00:00
}
// IsMetricsEnabled возвращает флаг включения метрик.
2026-10-05 21:26:41 +00:00
func ( m * MetricsConfig ) IsMetricsEnabled () bool { return m . Enabled }
2026-09-20 21:50:21 +00:00
// GetCollectInterval возвращает интервал сбора метрик как time.Duration.
func ( m * MetricsConfig ) GetCollectInterval () time . Duration {
if m . CollectIntervalSec <= 0 {
return 15 * time . Second
}
return time . Duration ( m . CollectIntervalSec ) * time . Second
}
// =============================================================================
// КОНФИГУРАЦИЯ КЛАСТЕРА
// =============================================================================
2026-10-05 21:26:41 +00:00
// ClusterConfig содержит настройки для работы в кластерном режиме.
2026-09-20 21:50:21 +00:00
type ClusterConfig struct {
Name string `toml:"name"`
2026-10-05 21:26:41 +00:00
NodeIP string `toml:"node_ip"`
NodePort int `toml:"node_port"`
RaftPort int `toml:"raft_port"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
RaftDataDir string `toml:"raft_data_dir"`
Bootstrap bool `toml:"bootstrap"`
Nodes [] string `toml:"nodes"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
HeartbeatTimeoutMs int `toml:"heartbeat_timeout_ms"`
ElectionTimeoutMs int `toml:"election_timeout_ms"`
CommitTimeoutMs int `toml:"commit_timeout_ms"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
SnapshotIntervalMin int `toml:"snapshot_interval_min"`
SnapshotThreshold int `toml:"snapshot_threshold"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
SplitBrainPrevention bool `toml:"split_brain_prevention"`
RecoveryTimeoutSec int `toml:"recovery_timeout_sec"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
Region string `toml:"region"`
PriorityZone int `toml:"priority_zone"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ SAGA
2026-09-20 21:50:21 +00:00
// =============================================================================
2026-10-05 21:26:41 +00:00
// SagaConfig содержит настройки SAGA оркестратора.
2026-09-20 21:50:21 +00:00
type SagaConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
CoordinatorCount int `toml:"coordinator_count"`
StateDir string `toml:"state_dir"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
MaxRetries int `toml:"max_retries"`
RetryBackoffMs int `toml:"retry_backoff_ms"`
SagaTimeoutSec int `toml:"saga_timeout_sec"`
StuckCheckIntervalSec int `toml:"stuck_check_interval_sec"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
LeaderElectionIntervalSec int `toml:"leader_election_interval_sec"`
RecoveryIntervalSec int `toml:"recovery_interval_sec"`
MetricsIntervalSec int `toml:"metrics_interval_sec"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
CleanupPeriodHours int `toml:"cleanup_period_hours"`
MaxCacheSize int `toml:"max_cache_size"`
OperationRetentionDays int `toml:"operation_retention_days"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
ChannelBufferSize int `toml:"channel_buffer_size"`
AsyncRecoveryWorkers int `toml:"async_recovery_workers"`
AsyncRecoveryTimeoutSec int `toml:"async_recovery_timeout_sec"`
2026-09-20 21:50:21 +00:00
2026-10-05 21:26:41 +00:00
FsyncEnabled bool `toml:"fsync_enabled"`
FsyncMaxRetries int `toml:"fsync_max_retries"`
FsyncRetryDelayMs int `toml:"fsync_retry_delay_ms"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ ХРАНИЛИЩА
// =============================================================================
type StorageConfig struct {
2026-10-05 21:26:41 +00:00
PageSizeMB int `toml:"page_size_mb"`
MaxCollections int `toml:"max_collections"`
MaxDocumentsPerCollection int `toml:"max_documents_per_collection"`
DefaultEngine string `toml:"default_engine"`
EnableCustomEngines bool `toml:"enable_custom_engines"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ REPL
2026-09-20 21:50:21 +00:00
// =============================================================================
type ReplConfig struct {
2026-10-05 21:26:41 +00:00
PromptColor string `toml:"prompt_color"`
HistorySize int `toml:"history_size"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ ЛОГИРОВАНИЯ
// =============================================================================
type LogConfig struct {
2026-10-05 21:26:41 +00:00
LogFile string `toml:"log_file"`
LogLevel string `toml:"log_level"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ HTTP API
// =============================================================================
type APIConfig struct {
2026-10-05 21:26:41 +00:00
Port int `toml:"port"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ РЕПЛИКАЦИИ
// =============================================================================
type ReplicationConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
SyncReplication bool `toml:"sync_replication"`
ReplicationTimeoutMs int `toml:"replication_timeout_ms"`
MaxReplicaLagMs int `toml:"max_replica_lag_ms"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ ПЛАГИНОВ (LUA)
// =============================================================================
type PluginsConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
ScriptDir string `toml:"script_dir"`
AllowList [] string `toml:"allow_list"`
MaxCPUTimeMs int `toml:"max_cpu_time_ms"`
MaxMemoryMB int `toml:"max_memory_mb"`
MaxExecutionTimeSec int `toml:"max_execution_time_sec"`
MaxInstructions int64 `toml:"max_instructions"`
HotReloadIntervalSec int `toml:"hot_reload_interval_sec"`
MaxEventLogSize int `toml:"max_event_log_size"`
LoadTimeoutSec int `toml:"load_timeout_sec"`
MaxLuaStates int `toml:"max_lua_states"`
LuaStateTTLSec int `toml:"lua_state_ttl_sec"`
EnginePluginDir string `toml:"engine_plugin_dir"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ СЖАТИЯ
// =============================================================================
type CompressionConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
Algorithm string `toml:"algorithm"`
Level int `toml:"level"`
MinSize int `toml:"min_size"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ ПРОИЗВОДИТЕЛЬНОСТИ
// =============================================================================
type PerformanceConfig struct {
2026-10-05 21:26:41 +00:00
EnablePipeline bool `toml:"enable_pipeline"`
BatchSize int `toml:"batch_size"`
ReadFromFollower bool `toml:"read_from_follower"`
MaxConnections int `toml:"max_connections"`
ReadReplicaDelayMs int `toml:"read_replica_delay_ms"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ БЕЗОПАСНОСТИ
// =============================================================================
type SecurityConfig struct {
2026-10-05 21:26:41 +00:00
EnableTLS bool `toml:"enable_tls"`
CertFile string `toml:"cert_file"`
KeyFile string `toml:"key_file"`
CAFile string `toml:"ca_file"`
MinVersion string `toml:"min_version"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ МОНИТОРИНГА
// =============================================================================
type MonitoringConfig struct {
2026-10-05 21:26:41 +00:00
EnableMetrics bool `toml:"enable_metrics"`
MetricsPort int `toml:"metrics_port"`
EnableTracing bool `toml:"enable_tracing"`
TraceSampleRate float64 `toml:"trace_sample_rate"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ ВОССТАНОВЛЕНИЯ
// =============================================================================
type RecoveryConfig struct {
2026-10-05 21:26:41 +00:00
AutoRejoin bool `toml:"auto_rejoin"`
MaxRetrySec int `toml:"max_retry_sec"`
DataReplicationTimeoutSec int `toml:"data_replication_timeout_sec"`
StaleReadTimeoutSec int `toml:"stale_read_timeout_sec"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ WAL
2026-09-20 21:50:21 +00:00
// =============================================================================
type WALConfig struct {
2026-10-05 21:26:41 +00:00
SegmentSizeMB int `toml:"segment_size_mb"`
SyncIntervalSec int `toml:"sync_interval_sec"`
BatchSize int `toml:"batch_size"`
RecoveryWorkers int `toml:"recovery_workers"`
Enabled bool `toml:"enabled"`
AsyncRecovery bool `toml:"async_recovery"`
AsyncRecoveryWorkers int `toml:"async_recovery_workers"`
AsyncRecoveryBuffer int `toml:"async_recovery_buffer"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ MVCC
2026-09-20 21:50:21 +00:00
// =============================================================================
type MVCCConfig struct {
2026-10-05 21:26:41 +00:00
MaxVersionsPerDoc int `toml:"max_versions_per_doc"`
VisibilityMapSize int `toml:"visibility_map_size"`
PruneIntervalMin int `toml:"prune_interval_min"`
RetentionDays int `toml:"retention_days"`
ReadCacheSize int `toml:"read_cache_size"`
ReadCacheTTLSec int `toml:"read_cache_ttl_sec"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ ТРАНЗАКЦИЙ
// =============================================================================
type TransactionsConfig struct {
2026-10-05 21:26:41 +00:00
DefaultTimeoutSec int `toml:"default_timeout_sec"`
DeadlockCheckIntervalSec int `toml:"deadlock_check_interval_sec"`
MaxSavepointsPerTx int `toml:"max_savepoints_per_tx"`
Enabled bool `toml:"enabled"`
CheckpointIntervalSec int `toml:"checkpoint_interval_sec"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ ACL
2026-09-20 21:50:21 +00:00
// =============================================================================
type ACLConfig struct {
2026-10-05 21:26:41 +00:00
MaxDeniedLogSize int `toml:"max_denied_log_size"`
TemporaryGrantTTLHours int `toml:"temporary_grant_ttl_hours"`
EnableRoleHierarchy bool `toml:"enable_role_hierarchy"`
CacheTTLSec int `toml:"cache_ttl_sec"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ TLS ДЛЯ КЛАСТЕРА
// =============================================================================
type TLSConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
CertFile string `toml:"cert_file"`
KeyFile string `toml:"key_file"`
CAFile string `toml:"ca_file"`
MinVersion string `toml:"min_version"`
MutualAuth bool `toml:"mutual_auth"`
KeyRotationDays int `toml:"key_rotation_days"`
AutoGenerate bool `toml:"auto_generate"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ BACKPRESSURE
2026-09-20 21:50:21 +00:00
// =============================================================================
type BackpressureConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
CPUThreshold float64 `toml:"cpu_threshold"`
MemoryThreshold float64 `toml:"memory_threshold"`
QueueSizeThreshold int `toml:"queue_size_threshold"`
ConnectionThreshold int `toml:"connection_threshold"`
CheckIntervalMs int `toml:"check_interval_ms"`
LowDelayMs int64 `toml:"low_delay_ms"`
MediumRejectProb uint32 `toml:"medium_reject_prob"`
HighRejectProb uint32 `toml:"high_reject_prob"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ RUNTIME LIMITS
2026-09-20 21:50:21 +00:00
// =============================================================================
type RuntimeLimitsConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
GlobalMaxDocSizeMB int `toml:"global_max_doc_size_mb"`
GlobalMaxCollSizeMB int64 `toml:"global_max_coll_size_mb"`
GlobalMaxDocsPerColl int64 `toml:"global_max_docs_per_coll"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ AUTOSCALING
2026-09-20 21:50:21 +00:00
// =============================================================================
type AutoscalingConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
MinNodes int `toml:"min_nodes"`
MaxNodes int `toml:"max_nodes"`
ScaleUpThreshold float64 `toml:"scale_up_threshold"`
ScaleDownThreshold float64 `toml:"scale_down_threshold"`
ScaleUpCooldownSec int `toml:"scale_up_cooldown_sec"`
ScaleDownCooldownSec int `toml:"scale_down_cooldown_sec"`
EvaluationIntervalSec int `toml:"evaluation_interval_sec"`
PredictiveEnabled bool `toml:"predictive_enabled"`
MaxScaleUpNodes int `toml:"max_scale_up_nodes"`
MaxScaleDownNodes int `toml:"max_scale_down_nodes"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ МИГРАЦИИ СХЕМЫ
// =============================================================================
type SchemaMigrationConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
MigrationDir string `toml:"migration_dir"`
AutoMigrate bool `toml:"auto_migrate"`
TargetVersion string `toml:"target_version"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
2026-10-05 21:26:41 +00:00
// КОНФИГУРАЦИЯ BACKUP
2026-09-20 21:50:21 +00:00
// =============================================================================
2026-10-05 21:26:41 +00:00
2026-09-20 21:50:21 +00:00
type BackupConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
BackupDir string `toml:"backup_dir"`
MaxConcurrent int `toml:"max_concurrent"`
CompressEnabled bool `toml:"compress_enabled"`
RetentionDays int `toml:"retention_days"`
2026-09-21 22:10:41 +00:00
2026-10-05 21:26:41 +00:00
IncludeSagaState bool `toml:"include_saga_state"`
EnableIncremental bool `toml:"enable_incremental"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ ДВИЖКОВ ХРАНЕНИЯ
// =============================================================================
type EnginesConfig struct {
2026-10-05 21:26:41 +00:00
Row EngineConfig `toml:"row"`
Columnar EngineConfig `toml:"columnar"`
Document EngineConfig `toml:"document"`
KV EngineConfig `toml:"kv"`
TS EngineConfig `toml:"ts"`
Graph EngineConfig `toml:"graph"`
2026-09-20 21:50:21 +00:00
}
type EngineConfig struct {
2026-10-05 21:26:41 +00:00
Enabled bool `toml:"enabled"`
Description string `toml:"description"`
Config map [ string ] interface {} `toml:"config"`
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// КОНФИГУРАЦИЯ КРОСС-ДАТАЦЕНТРОВОЙ МИГРАЦИИ
// =============================================================================
type MigrationConfig struct {
Enabled bool `toml:"enabled"`
2026-10-05 21:26:41 +00:00
Mode string `toml:"mode"`
2026-09-20 21:50:21 +00:00
Source * DatacenterConfig `toml:"source"`
Target * DatacenterConfig `toml:"target"`
Settings * MigrationSettings `toml:"settings"`
Delta * DeltaSyncConfig `toml:"delta"`
Validation * ValidationConfig `toml:"validation"`
}
type DatacenterConfig struct {
Name string `toml:"name"`
Endpoint string `toml:"endpoint"`
APIKey string `toml:"api_key"`
TimeoutSec int `toml:"timeout_sec"`
}
type MigrationSettings struct {
BatchSize int `toml:"batch_size"`
Workers int `toml:"workers"`
Compression string `toml:"compression"`
ResumeEnabled bool `toml:"resume_enabled"`
CheckpointIntervalSec int `toml:"checkpoint_interval_sec"`
MaxRetries int `toml:"max_retries"`
RetryBackoffSec int `toml:"retry_backoff_sec"`
Collections [] string `toml:"collections"`
ExcludeCollections [] string `toml:"exclude_collections"`
}
type DeltaSyncConfig struct {
Enabled bool `toml:"enabled"`
IntervalSec int `toml:"interval_sec"`
MaxLagSec int `toml:"max_lag_sec"`
}
type ValidationConfig struct {
Enabled bool `toml:"enabled"`
SamplePercent int `toml:"sample_percent"`
MaxErrors int `toml:"max_errors"`
}
// =============================================================================
// ВСПОМОГАТЕЛЬНЫЕ СТРУКТУРЫ
// =============================================================================
type ValidationResult struct {
2026-10-05 21:26:41 +00:00
Valid bool
Errors [] error
Warnings [] error
2026-09-20 21:50:21 +00:00
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ SagaConfig
// =============================================================================
func ( s * SagaConfig ) IsSagaEnabled () bool { return s . Enabled }
func ( s * SagaConfig ) GetCoordinatorCount () int {
if s . CoordinatorCount < 1 {
return 3
}
return s . CoordinatorCount
}
func ( s * SagaConfig ) GetStateDir () string {
if s . StateDir == "" {
return "saga_states"
}
return s . StateDir
}
func ( s * SagaConfig ) GetMaxRetries () int {
if s . MaxRetries <= 0 {
return 5
}
return s . MaxRetries
}
func ( s * SagaConfig ) GetRetryBackoff () time . Duration {
if s . RetryBackoffMs <= 0 {
return 100 * time . Millisecond
}
return time . Duration ( s . RetryBackoffMs ) * time . Millisecond
}
func ( s * SagaConfig ) GetSagaTimeout () time . Duration {
if s . SagaTimeoutSec <= 0 {
return 300 * time . Second
}
return time . Duration ( s . SagaTimeoutSec ) * time . Second
}
func ( s * SagaConfig ) GetStuckCheckInterval () time . Duration {
if s . StuckCheckIntervalSec <= 0 {
return 10 * time . Second
}
return time . Duration ( s . StuckCheckIntervalSec ) * time . Second
}
func ( s * SagaConfig ) GetLeaderElectionInterval () time . Duration {
if s . LeaderElectionIntervalSec <= 0 {
return 5 * time . Second
}
return time . Duration ( s . LeaderElectionIntervalSec ) * time . Second
}
func ( s * SagaConfig ) GetRecoveryInterval () time . Duration {
if s . RecoveryIntervalSec <= 0 {
return 30 * time . Second
}
return time . Duration ( s . RecoveryIntervalSec ) * time . Second
}
func ( s * SagaConfig ) GetMetricsInterval () time . Duration {
if s . MetricsIntervalSec <= 0 {
return 60 * time . Second
}
return time . Duration ( s . MetricsIntervalSec ) * time . Second
}
func ( s * SagaConfig ) GetCleanupPeriod () time . Duration {
if s . CleanupPeriodHours <= 0 {
return 24 * time . Hour
}
return time . Duration ( s . CleanupPeriodHours ) * time . Hour
}
func ( s * SagaConfig ) GetMaxCacheSize () int {
if s . MaxCacheSize <= 0 {
return 10000
}
return s . MaxCacheSize
}
func ( s * SagaConfig ) GetOperationRetentionDuration () time . Duration {
if s . OperationRetentionDays <= 0 {
return 7 * 24 * time . Hour
}
return time . Duration ( s . OperationRetentionDays ) * 24 * time . Hour
}
func ( s * SagaConfig ) GetChannelBufferSize () int {
if s . ChannelBufferSize <= 0 {
return 10000
}
return s . ChannelBufferSize
}
func ( s * SagaConfig ) GetAsyncRecoveryWorkers () int {
if s . AsyncRecoveryWorkers <= 0 {
return 4
}
return s . AsyncRecoveryWorkers
}
func ( s * SagaConfig ) GetAsyncRecoveryTimeout () time . Duration {
if s . AsyncRecoveryTimeoutSec <= 0 {
return 30 * time . Second
}
return time . Duration ( s . AsyncRecoveryTimeoutSec ) * time . Second
}
func ( s * SagaConfig ) IsFsyncEnabled () bool { return s . FsyncEnabled }
func ( s * SagaConfig ) GetFsyncMaxRetries () int {
if s . FsyncMaxRetries <= 0 {
return 3
}
return s . FsyncMaxRetries
}
func ( s * SagaConfig ) GetFsyncRetryDelay () time . Duration {
if s . FsyncRetryDelayMs <= 0 {
return 100 * time . Millisecond
}
return time . Duration ( s . FsyncRetryDelayMs ) * time . Millisecond
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ ReplicationConfig
// =============================================================================
func ( r * ReplicationConfig ) GetReplicationTimeout () time . Duration {
if r . ReplicationTimeoutMs <= 0 {
return 5 * time . Second
}
return time . Duration ( r . ReplicationTimeoutMs ) * time . Millisecond
}
func ( r * ReplicationConfig ) IsReplicationEnabled () bool { return r . Enabled }
func ( r * ReplicationConfig ) IsSyncReplicationEnabled () bool { return r . SyncReplication }
func ( r * ReplicationConfig ) GetMaxReplicaLag () time . Duration {
if r . MaxReplicaLagMs <= 0 {
return 5 * time . Second
}
return time . Duration ( r . MaxReplicaLagMs ) * time . Millisecond
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ ClusterConfig
// =============================================================================
func ( c * ClusterConfig ) GetHeartbeatTimeout () time . Duration {
if c . HeartbeatTimeoutMs <= 0 {
return 1000 * time . Millisecond
}
return time . Duration ( c . HeartbeatTimeoutMs ) * time . Millisecond
}
func ( c * ClusterConfig ) GetElectionTimeout () time . Duration {
if c . ElectionTimeoutMs <= 0 {
return 1000 * time . Millisecond
}
return time . Duration ( c . ElectionTimeoutMs ) * time . Millisecond
}
func ( c * ClusterConfig ) GetCommitTimeout () time . Duration {
if c . CommitTimeoutMs <= 0 {
return 500 * time . Millisecond
}
return time . Duration ( c . CommitTimeoutMs ) * time . Millisecond
}
func ( c * ClusterConfig ) GetSnapshotInterval () time . Duration {
if c . SnapshotIntervalMin <= 0 {
return 30 * time . Minute
}
return time . Duration ( c . SnapshotIntervalMin ) * time . Minute
}
func ( c * ClusterConfig ) GetSnapshotThreshold () uint64 {
if c . SnapshotThreshold <= 0 {
return 1000
}
return uint64 ( c . SnapshotThreshold )
}
func ( c * ClusterConfig ) IsSplitBrainPreventionEnabled () bool { return c . SplitBrainPrevention }
func ( c * ClusterConfig ) GetRecoveryTimeout () time . Duration {
if c . RecoveryTimeoutSec <= 0 {
return 30 * time . Second
}
return time . Duration ( c . RecoveryTimeoutSec ) * time . Second
}
func ( c * ClusterConfig ) GetRegion () string {
if c . Region == "" {
return "default"
}
return c . Region
}
func ( c * ClusterConfig ) GetPriorityZone () int {
if c . PriorityZone < 0 {
return 0
}
if c . PriorityZone > 9 {
return 9
}
return c . PriorityZone
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ PluginsConfig
// =============================================================================
func ( p * PluginsConfig ) GetMaxCPUTime () time . Duration {
if p . MaxCPUTimeMs <= 0 {
return 100 * time . Millisecond
}
return time . Duration ( p . MaxCPUTimeMs ) * time . Millisecond
}
func ( p * PluginsConfig ) GetMaxMemory () int64 {
if p . MaxMemoryMB <= 0 {
return 50 * 1024 * 1024
}
return int64 ( p . MaxMemoryMB ) * 1024 * 1024
}
func ( p * PluginsConfig ) GetMaxExecutionTime () time . Duration {
if p . MaxExecutionTimeSec <= 0 {
return 5 * time . Second
}
return time . Duration ( p . MaxExecutionTimeSec ) * time . Second
}
func ( p * PluginsConfig ) GetMaxInstructions () int64 {
if p . MaxInstructions <= 0 {
return 1000000
}
return p . MaxInstructions
}
func ( p * PluginsConfig ) GetHotReloadInterval () time . Duration {
if p . HotReloadIntervalSec <= 0 {
return 30 * time . Second
}
return time . Duration ( p . HotReloadIntervalSec ) * time . Second
}
func ( p * PluginsConfig ) GetMaxEventLogSize () int {
if p . MaxEventLogSize <= 0 {
return 1000
}
return p . MaxEventLogSize
}
func ( p * PluginsConfig ) GetLoadTimeout () time . Duration {
if p . LoadTimeoutSec <= 0 {
return 10 * time . Second
}
return time . Duration ( p . LoadTimeoutSec ) * time . Second
}
func ( p * PluginsConfig ) GetMaxLuaStates () int {
if p . MaxLuaStates <= 0 {
return 100
}
return p . MaxLuaStates
}
func ( p * PluginsConfig ) GetLuaStateTTL () time . Duration {
if p . LuaStateTTLSec <= 0 {
return 10 * time . Minute
}
return time . Duration ( p . LuaStateTTLSec ) * time . Second
}
func ( p * PluginsConfig ) GetEnginePluginDir () string {
if p . EnginePluginDir == "" {
return "engines"
}
return p . EnginePluginDir
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ WALConfig
// =============================================================================
func ( w * WALConfig ) GetSegmentSize () int64 {
if w . SegmentSizeMB <= 0 {
return 64 * 1024 * 1024
}
return int64 ( w . SegmentSizeMB ) * 1024 * 1024
}
func ( w * WALConfig ) GetSyncInterval () time . Duration {
if w . SyncIntervalSec <= 0 {
return 5 * time . Second
}
return time . Duration ( w . SyncIntervalSec ) * time . Second
}
func ( w * WALConfig ) GetBatchSize () int {
if w . BatchSize <= 0 {
return 100
}
return w . BatchSize
}
func ( w * WALConfig ) GetRecoveryWorkers () int {
if w . RecoveryWorkers <= 0 {
return 4
}
return w . RecoveryWorkers
}
func ( w * WALConfig ) IsWALEnabled () bool { return w . Enabled }
func ( w * WALConfig ) IsAsyncRecoveryEnabled () bool { return w . AsyncRecovery }
func ( w * WALConfig ) GetAsyncRecoveryWorkers () int {
if w . AsyncRecoveryWorkers <= 0 {
return 4
}
return w . AsyncRecoveryWorkers
}
func ( w * WALConfig ) GetAsyncRecoveryBuffer () int {
if w . AsyncRecoveryBuffer <= 0 {
return 10000
}
return w . AsyncRecoveryBuffer
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ MVCCConfig
// =============================================================================
func ( m * MVCCConfig ) GetMaxVersionsPerDoc () int {
if m . MaxVersionsPerDoc <= 0 {
return 10
}
return m . MaxVersionsPerDoc
}
func ( m * MVCCConfig ) GetVisibilityMapSize () int {
if m . VisibilityMapSize <= 0 {
return 1024 * 1024
}
return m . VisibilityMapSize
}
func ( m * MVCCConfig ) GetPruneInterval () time . Duration {
if m . PruneIntervalMin <= 0 {
return 5 * time . Minute
}
return time . Duration ( m . PruneIntervalMin ) * time . Minute
}
func ( m * MVCCConfig ) GetRetentionDays () int {
if m . RetentionDays <= 0 {
return 7
}
return m . RetentionDays
}
func ( m * MVCCConfig ) GetReadCacheSize () int {
if m . ReadCacheSize <= 0 {
return 10000
}
return m . ReadCacheSize
}
func ( m * MVCCConfig ) GetReadCacheTTL () time . Duration {
if m . ReadCacheTTLSec <= 0 {
return 300 * time . Second
}
return time . Duration ( m . ReadCacheTTLSec ) * time . Second
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ TransactionsConfig
// =============================================================================
func ( t * TransactionsConfig ) GetDefaultTimeout () time . Duration {
if t . DefaultTimeoutSec <= 0 {
return 30 * time . Second
}
return time . Duration ( t . DefaultTimeoutSec ) * time . Second
}
func ( t * TransactionsConfig ) GetDeadlockCheckInterval () time . Duration {
if t . DeadlockCheckIntervalSec <= 0 {
return 1 * time . Second
}
return time . Duration ( t . DeadlockCheckIntervalSec ) * time . Second
}
func ( t * TransactionsConfig ) GetMaxSavepointsPerTx () int {
if t . MaxSavepointsPerTx <= 0 {
return 100
}
return t . MaxSavepointsPerTx
}
func ( t * TransactionsConfig ) IsTransactionsEnabled () bool { return t . Enabled }
func ( t * TransactionsConfig ) GetCheckpointInterval () int64 {
if t . CheckpointIntervalSec <= 0 {
return 300
}
return int64 ( t . CheckpointIntervalSec )
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ ACLConfig
// =============================================================================
func ( a * ACLConfig ) GetMaxDeniedLogSize () int {
if a . MaxDeniedLogSize <= 0 {
return 10000
}
return a . MaxDeniedLogSize
}
func ( a * ACLConfig ) GetTemporaryGrantTTL () time . Duration {
if a . TemporaryGrantTTLHours <= 0 {
return 24 * time . Hour
}
return time . Duration ( a . TemporaryGrantTTLHours ) * time . Hour
}
func ( a * ACLConfig ) IsRoleHierarchyEnabled () bool { return a . EnableRoleHierarchy }
func ( a * ACLConfig ) GetCacheTTL () time . Duration {
if a . CacheTTLSec <= 0 {
return 60 * time . Second
}
return time . Duration ( a . CacheTTLSec ) * time . Second
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ TLSConfig
// =============================================================================
func ( t * TLSConfig ) GetTLSMinVersion () string {
if t . MinVersion == "" {
return "1.2"
}
return t . MinVersion
}
func ( t * TLSConfig ) IsTLSEnabled () bool { return t . Enabled }
func ( t * TLSConfig ) IsMutualAuthEnabled () bool { return t . MutualAuth }
func ( t * TLSConfig ) GetKeyRotationDays () int {
if t . KeyRotationDays <= 0 {
return 30
}
return t . KeyRotationDays
}
func ( t * TLSConfig ) IsAutoGenerateEnabled () bool { return t . AutoGenerate }
// =============================================================================
// ГЕТТЕРЫ ДЛЯ BackpressureConfig
// =============================================================================
func ( b * BackpressureConfig ) IsBackpressureEnabled () bool { return b . Enabled }
func ( b * BackpressureConfig ) GetCPUThreshold () float64 {
if b . CPUThreshold <= 0 {
return 0.8
}
if b . CPUThreshold > 1.0 {
return 1.0
}
return b . CPUThreshold
}
func ( b * BackpressureConfig ) GetMemoryThreshold () float64 {
if b . MemoryThreshold <= 0 {
return 0.85
}
if b . MemoryThreshold > 1.0 {
return 1.0
}
return b . MemoryThreshold
}
func ( b * BackpressureConfig ) GetQueueSizeThreshold () int {
if b . QueueSizeThreshold <= 0 {
return 10000
}
return b . QueueSizeThreshold
}
func ( b * BackpressureConfig ) GetConnectionThreshold () int {
if b . ConnectionThreshold <= 0 {
return 5000
}
return b . ConnectionThreshold
}
func ( b * BackpressureConfig ) GetCheckInterval () time . Duration {
if b . CheckIntervalMs <= 0 {
return 1 * time . Second
}
return time . Duration ( b . CheckIntervalMs ) * time . Millisecond
}
func ( b * BackpressureConfig ) GetLowDelay () time . Duration {
if b . LowDelayMs <= 0 {
return 100 * time . Millisecond
}
return time . Duration ( b . LowDelayMs ) * time . Millisecond
}
func ( b * BackpressureConfig ) GetMediumRejectProb () uint32 {
if b . MediumRejectProb > 100 {
return 100
}
return b . MediumRejectProb
}
func ( b * BackpressureConfig ) GetHighRejectProb () uint32 {
if b . HighRejectProb > 100 {
return 100
}
return b . HighRejectProb
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ RuntimeLimitsConfig
// =============================================================================
func ( r * RuntimeLimitsConfig ) IsRuntimeLimitsEnabled () bool { return r . Enabled }
func ( r * RuntimeLimitsConfig ) GetGlobalMaxDocSizeMB () int {
if r . GlobalMaxDocSizeMB <= 0 {
return 16
}
return r . GlobalMaxDocSizeMB
}
func ( r * RuntimeLimitsConfig ) GetGlobalMaxCollSizeMB () int64 {
if r . GlobalMaxCollSizeMB <= 0 {
return 10240
}
return r . GlobalMaxCollSizeMB
}
func ( r * RuntimeLimitsConfig ) GetGlobalMaxDocsPerColl () int64 {
if r . GlobalMaxDocsPerColl <= 0 {
return 10000000
}
return r . GlobalMaxDocsPerColl
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ AutoscalingConfig
// =============================================================================
func ( a * AutoscalingConfig ) IsAutoscalingEnabled () bool { return a . Enabled }
func ( a * AutoscalingConfig ) GetMinNodes () int {
if a . MinNodes <= 0 {
return 1
}
return a . MinNodes
}
func ( a * AutoscalingConfig ) GetMaxNodes () int {
if a . MaxNodes <= 0 {
return 10
}
return a . MaxNodes
}
func ( a * AutoscalingConfig ) GetScaleUpThreshold () float64 {
if a . ScaleUpThreshold <= 0 {
return 0.75
}
if a . ScaleUpThreshold > 1.0 {
return 1.0
}
return a . ScaleUpThreshold
}
func ( a * AutoscalingConfig ) GetScaleDownThreshold () float64 {
if a . ScaleDownThreshold <= 0 {
return 0.30
}
return a . ScaleDownThreshold
}
func ( a * AutoscalingConfig ) GetScaleUpCooldown () time . Duration {
if a . ScaleUpCooldownSec <= 0 {
return 5 * time . Minute
}
return time . Duration ( a . ScaleUpCooldownSec ) * time . Second
}
func ( a * AutoscalingConfig ) GetScaleDownCooldown () time . Duration {
if a . ScaleDownCooldownSec <= 0 {
return 10 * time . Minute
}
return time . Duration ( a . ScaleDownCooldownSec ) * time . Second
}
func ( a * AutoscalingConfig ) GetEvaluationInterval () time . Duration {
if a . EvaluationIntervalSec <= 0 {
return 30 * time . Second
}
return time . Duration ( a . EvaluationIntervalSec ) * time . Second
}
func ( a * AutoscalingConfig ) IsPredictiveEnabled () bool { return a . PredictiveEnabled }
func ( a * AutoscalingConfig ) GetMaxScaleUpNodes () int {
if a . MaxScaleUpNodes <= 0 {
return 3
}
return a . MaxScaleUpNodes
}
func ( a * AutoscalingConfig ) GetMaxScaleDownNodes () int {
if a . MaxScaleDownNodes <= 0 {
return 2
}
return a . MaxScaleDownNodes
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ SchemaMigrationConfig
// =============================================================================
func ( s * SchemaMigrationConfig ) IsSchemaMigrationEnabled () bool { return s . Enabled }
func ( s * SchemaMigrationConfig ) GetMigrationDir () string {
if s . MigrationDir == "" {
return "migrations"
}
return s . MigrationDir
}
func ( s * SchemaMigrationConfig ) IsAutoMigrateEnabled () bool { return s . AutoMigrate }
func ( s * SchemaMigrationConfig ) GetTargetVersion () string {
if s . TargetVersion == "" {
return "latest"
}
return s . TargetVersion
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ BackupConfig
// =============================================================================
func ( b * BackupConfig ) IsBackupEnabled () bool { return b . Enabled }
func ( b * BackupConfig ) GetBackupDir () string {
if b . BackupDir == "" {
return "backups"
}
return b . BackupDir
}
func ( b * BackupConfig ) GetMaxConcurrentBackups () int {
if b . MaxConcurrent <= 0 {
return 1
}
return b . MaxConcurrent
}
func ( b * BackupConfig ) IsCompressEnabled () bool { return b . CompressEnabled }
func ( b * BackupConfig ) GetBackupRetentionDays () int {
if b . RetentionDays <= 0 {
return 7
}
return b . RetentionDays
}
2026-09-21 22:10:41 +00:00
func ( b * BackupConfig ) IsIncludeSagaStateEnabled () bool { return b . IncludeSagaState }
func ( b * BackupConfig ) IsIncrementalEnabled () bool { return b . EnableIncremental }
2026-09-20 21:50:21 +00:00
// =============================================================================
// ГЕТТЕРЫ ДЛЯ StorageConfig
// =============================================================================
func ( s * StorageConfig ) GetDefaultEngine () string {
if s . DefaultEngine == "" {
return "row"
}
return s . DefaultEngine
}
func ( s * StorageConfig ) IsCustomEnginesEnabled () bool { return s . EnableCustomEngines }
// =============================================================================
// МЕТОДЫ ДЛЯ EnginesConfig
// =============================================================================
func ( e * EnginesConfig ) IsEngineEnabled ( name string ) bool {
switch name {
case "row" :
return e . Row . Enabled
case "columnar" :
return e . Columnar . Enabled
case "document" :
return e . Document . Enabled
case "kv" :
return e . KV . Enabled
case "ts" :
return e . TS . Enabled
case "graph" :
return e . Graph . Enabled
default :
return false
}
}
func ( e * EnginesConfig ) GetEngineConfig ( name string ) map [ string ] interface {} {
switch name {
case "row" :
return e . Row . Config
case "columnar" :
return e . Columnar . Config
case "document" :
return e . Document . Config
case "kv" :
return e . KV . Config
case "ts" :
return e . TS . Config
case "graph" :
return e . Graph . Config
default :
return make ( map [ string ] interface {})
}
}
func ( e * EnginesConfig ) GetEngineDescription ( name string ) string {
switch name {
case "row" :
return e . Row . Description
case "columnar" :
return e . Columnar . Description
case "document" :
return e . Document . Description
case "kv" :
return e . KV . Description
case "ts" :
return e . TS . Description
case "graph" :
return e . Graph . Description
default :
return ""
}
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ RecoveryConfig
// =============================================================================
func ( r * RecoveryConfig ) GetMaxRetryDuration () time . Duration {
if r . MaxRetrySec <= 0 {
return 300 * time . Second
}
return time . Duration ( r . MaxRetrySec ) * time . Second
}
func ( r * RecoveryConfig ) GetDataReplicationTimeout () time . Duration {
if r . DataReplicationTimeoutSec <= 0 {
return 60 * time . Second
}
return time . Duration ( r . DataReplicationTimeoutSec ) * time . Second
}
func ( r * RecoveryConfig ) GetStaleReadTimeout () time . Duration {
if r . StaleReadTimeoutSec <= 0 {
return 30 * time . Second
}
return time . Duration ( r . StaleReadTimeoutSec ) * time . Second
}
func ( r * RecoveryConfig ) IsAutoRejoinEnabled () bool { return r . AutoRejoin }
// =============================================================================
// ГЕТТЕРЫ ДЛЯ PerformanceConfig
// =============================================================================
func ( p * PerformanceConfig ) GetBatchSize () int {
if p . BatchSize <= 0 {
return 100
}
return p . BatchSize
}
func ( p * PerformanceConfig ) GetMaxConnections () int {
if p . MaxConnections <= 0 {
return 1000
}
return p . MaxConnections
}
func ( p * PerformanceConfig ) IsReadFromFollowerEnabled () bool { return p . ReadFromFollower }
func ( p * PerformanceConfig ) IsPipelineEnabled () bool { return p . EnablePipeline }
func ( p * PerformanceConfig ) GetReadReplicaDelay () time . Duration {
if p . ReadReplicaDelayMs <= 0 {
return 100 * time . Millisecond
}
return time . Duration ( p . ReadReplicaDelayMs ) * time . Millisecond
}
// =============================================================================
// ГЕТТЕРЫ ДЛЯ MonitoringConfig
// =============================================================================
func ( m * MonitoringConfig ) GetMetricsPort () int {
if m . MetricsPort <= 0 {
return 9090
}
return m . MetricsPort
}
func ( m * MonitoringConfig ) GetTraceSampleRate () float64 {
if m . TraceSampleRate <= 0 || m . TraceSampleRate > 1 {
return 0.01
}
return m . TraceSampleRate
}
func ( m * MonitoringConfig ) IsMetricsEnabled () bool { return m . EnableMetrics }
func ( m * MonitoringConfig ) IsTracingEnabled () bool { return m . EnableTracing }
// =============================================================================
// ГЕТТЕРЫ ДЛЯ SecurityConfig
// =============================================================================
func ( s * SecurityConfig ) GetTLSMinVersion () uint16 {
switch s . MinVersion {
case "1.0" :
return 0x0301
case "1.1" :
return 0x0302
case "1.2" :
return 0x0303
case "1.3" :
return 0x0304
default :
return 0x0303
}
}
func ( s * SecurityConfig ) IsTLSEnabled () bool {
return s . EnableTLS && s . CertFile != "" && s . KeyFile != ""
}
func ( s * SecurityConfig ) GetCertFile () string { return s . CertFile }
func ( s * SecurityConfig ) GetKeyFile () string { return s . KeyFile }
func ( s * SecurityConfig ) GetCAFile () string { return s . CAFile }
// =============================================================================
// ГЕТТЕРЫ ДЛЯ CompressionConfig
// =============================================================================
func ( c * CompressionConfig ) IsCompressionEnabled () bool { return c . Enabled }
func ( c * CompressionConfig ) GetAlgorithm () string {
if c . Algorithm == "" {
return "snappy"
}
return c . Algorithm
}
func ( c * CompressionConfig ) GetLevel () int {
if c . Level < 1 {
return 3
}
if c . Level > 9 {
return 9
}
return c . Level
}
func ( c * CompressionConfig ) GetMinSize () int {
if c . MinSize <= 0 {
return 1024
}
return c . MinSize
}
// =============================================================================
// ЗАГРУЗКА И ВАЛИДАЦИЯ КОНФИГУРАЦИИ
// =============================================================================
// LoadConfig загружает и валидирует конфигурацию из TOML-файла.
func LoadConfig ( path string ) ( * Config , error ) {
info , err := os . Stat ( path )
if err != nil {
if os . IsNotExist ( err ) {
return nil , fmt . Errorf ( "config file not found: %s" , path )
}
return nil , fmt . Errorf ( "failed to stat config file: %w" , err )
}
if ! info . Mode (). IsRegular () {
return nil , fmt . Errorf ( "config path is not a regular file: %s" , path )
}
if info . Size () > maxConfigFileSize {
return nil , fmt . Errorf ( "config file too large: %d bytes (max %d)" , info . Size (), maxConfigFileSize )
}
var cfg Config
if _ , err := toml . DecodeFile ( path , & cfg ); err != nil {
return nil , fmt . Errorf ( "failed to decode config file: %v" , err )
}
// ===== УСТАНОВКА ЗНАЧЕНИЙ ПО УМОЛЧАНИЮ =====
if cfg . Cluster . RaftPort == 0 {
cfg . Cluster . RaftPort = 9878
}
if cfg . Cluster . NodePort == 0 {
cfg . Cluster . NodePort = 9877
}
if cfg . Cluster . NodeIP == "" {
cfg . Cluster . NodeIP = "0.0.0.0"
}
if cfg . Cluster . RaftDataDir == "" {
cfg . Cluster . RaftDataDir = "raft_data"
}
if cfg . Cluster . HeartbeatTimeoutMs == 0 {
cfg . Cluster . HeartbeatTimeoutMs = 1000
}
if cfg . Cluster . ElectionTimeoutMs == 0 {
cfg . Cluster . ElectionTimeoutMs = 1000
}
if cfg . Cluster . CommitTimeoutMs == 0 {
cfg . Cluster . CommitTimeoutMs = 500
}
if cfg . Cluster . SnapshotIntervalMin == 0 {
cfg . Cluster . SnapshotIntervalMin = 30
}
if cfg . Cluster . SnapshotThreshold == 0 {
cfg . Cluster . SnapshotThreshold = 1000
}
if cfg . Cluster . RecoveryTimeoutSec == 0 {
cfg . Cluster . RecoveryTimeoutSec = 30
}
if cfg . Cluster . Region == "" {
cfg . Cluster . Region = "default"
}
if cfg . Saga . CoordinatorCount == 0 {
cfg . Saga . CoordinatorCount = 3
}
if cfg . Saga . StateDir == "" {
cfg . Saga . StateDir = "saga_states"
}
if cfg . Saga . MaxRetries == 0 {
cfg . Saga . MaxRetries = 5
}
if cfg . Saga . RetryBackoffMs == 0 {
cfg . Saga . RetryBackoffMs = 100
}
if cfg . Saga . SagaTimeoutSec == 0 {
cfg . Saga . SagaTimeoutSec = 300
}
if cfg . Saga . StuckCheckIntervalSec == 0 {
cfg . Saga . StuckCheckIntervalSec = 10
}
if cfg . Saga . LeaderElectionIntervalSec == 0 {
cfg . Saga . LeaderElectionIntervalSec = 5
}
if cfg . Saga . RecoveryIntervalSec == 0 {
cfg . Saga . RecoveryIntervalSec = 30
}
if cfg . Saga . MetricsIntervalSec == 0 {
cfg . Saga . MetricsIntervalSec = 60
}
if cfg . Saga . CleanupPeriodHours == 0 {
cfg . Saga . CleanupPeriodHours = 24
}
if cfg . Saga . MaxCacheSize == 0 {
cfg . Saga . MaxCacheSize = 10000
}
if cfg . Saga . OperationRetentionDays == 0 {
cfg . Saga . OperationRetentionDays = 7
}
if cfg . Saga . ChannelBufferSize == 0 {
cfg . Saga . ChannelBufferSize = 10000
}
if cfg . Saga . AsyncRecoveryWorkers == 0 {
cfg . Saga . AsyncRecoveryWorkers = 4
}
if cfg . Saga . AsyncRecoveryTimeoutSec == 0 {
cfg . Saga . AsyncRecoveryTimeoutSec = 30
}
if cfg . Saga . FsyncMaxRetries == 0 {
cfg . Saga . FsyncMaxRetries = 3
}
if cfg . Saga . FsyncRetryDelayMs == 0 {
cfg . Saga . FsyncRetryDelayMs = 100
}
if cfg . Replication . ReplicationTimeoutMs == 0 {
cfg . Replication . ReplicationTimeoutMs = 5000
}
if cfg . Replication . MaxReplicaLagMs == 0 {
cfg . Replication . MaxReplicaLagMs = 5000
}
if cfg . Plugins . ScriptDir == "" {
cfg . Plugins . ScriptDir = "plugins"
}
if cfg . Plugins . EnginePluginDir == "" {
cfg . Plugins . EnginePluginDir = "engines"
}
if cfg . Plugins . MaxCPUTimeMs == 0 {
cfg . Plugins . MaxCPUTimeMs = 100
}
if cfg . Plugins . MaxMemoryMB == 0 {
cfg . Plugins . MaxMemoryMB = 50
}
if cfg . Plugins . MaxExecutionTimeSec == 0 {
cfg . Plugins . MaxExecutionTimeSec = 5
}
if cfg . Plugins . MaxInstructions == 0 {
cfg . Plugins . MaxInstructions = 1000000
}
if cfg . Plugins . HotReloadIntervalSec == 0 {
cfg . Plugins . HotReloadIntervalSec = 30
}
if cfg . Plugins . MaxEventLogSize == 0 {
cfg . Plugins . MaxEventLogSize = 1000
}
if cfg . Plugins . LoadTimeoutSec == 0 {
cfg . Plugins . LoadTimeoutSec = 10
}
if cfg . Plugins . MaxLuaStates == 0 {
cfg . Plugins . MaxLuaStates = 100
}
if cfg . Plugins . LuaStateTTLSec == 0 {
cfg . Plugins . LuaStateTTLSec = 600
}
if cfg . API . Port == 0 {
cfg . API . Port = 8080
}
if cfg . Metrics . CollectIntervalSec == 0 {
cfg . Metrics . CollectIntervalSec = 15
}
if cfg . Compression . Algorithm == "" {
cfg . Compression . Algorithm = "snappy"
}
if cfg . Compression . MinSize == 0 {
cfg . Compression . MinSize = 1024
}
if cfg . Compression . Level == 0 {
cfg . Compression . Level = 3
}
if cfg . Performance . BatchSize == 0 {
cfg . Performance . BatchSize = 100
}
if cfg . Performance . MaxConnections == 0 {
cfg . Performance . MaxConnections = 1000
}
if cfg . Performance . ReadReplicaDelayMs == 0 {
cfg . Performance . ReadReplicaDelayMs = 100
}
if cfg . Monitoring . MetricsPort == 0 {
cfg . Monitoring . MetricsPort = 9090
}
if cfg . Monitoring . TraceSampleRate == 0 {
cfg . Monitoring . TraceSampleRate = 0.01
}
if cfg . Recovery . MaxRetrySec == 0 {
cfg . Recovery . MaxRetrySec = 300
}
if cfg . Recovery . DataReplicationTimeoutSec == 0 {
cfg . Recovery . DataReplicationTimeoutSec = 60
}
if cfg . Recovery . StaleReadTimeoutSec == 0 {
cfg . Recovery . StaleReadTimeoutSec = 30
}
if cfg . Security . MinVersion == "" {
cfg . Security . MinVersion = "1.2"
}
if cfg . Storage . DefaultEngine == "" {
cfg . Storage . DefaultEngine = "row"
}
if cfg . WAL . SegmentSizeMB == 0 {
cfg . WAL . SegmentSizeMB = 64
}
if cfg . WAL . SyncIntervalSec == 0 {
cfg . WAL . SyncIntervalSec = 5
}
if cfg . WAL . BatchSize == 0 {
cfg . WAL . BatchSize = 100
}
if cfg . WAL . RecoveryWorkers == 0 {
cfg . WAL . RecoveryWorkers = 4
}
if cfg . WAL . AsyncRecoveryWorkers == 0 {
cfg . WAL . AsyncRecoveryWorkers = 4
}
if cfg . WAL . AsyncRecoveryBuffer == 0 {
cfg . WAL . AsyncRecoveryBuffer = 10000
}
if cfg . MVCC . MaxVersionsPerDoc == 0 {
cfg . MVCC . MaxVersionsPerDoc = 10
}
if cfg . MVCC . VisibilityMapSize == 0 {
cfg . MVCC . VisibilityMapSize = 1024 * 1024
}
if cfg . MVCC . PruneIntervalMin == 0 {
cfg . MVCC . PruneIntervalMin = 5
}
if cfg . MVCC . RetentionDays == 0 {
cfg . MVCC . RetentionDays = 7
}
if cfg . MVCC . ReadCacheSize == 0 {
cfg . MVCC . ReadCacheSize = 10000
}
if cfg . MVCC . ReadCacheTTLSec == 0 {
cfg . MVCC . ReadCacheTTLSec = 300
}
if cfg . Transactions . DefaultTimeoutSec == 0 {
cfg . Transactions . DefaultTimeoutSec = 30
}
if cfg . Transactions . DeadlockCheckIntervalSec == 0 {
cfg . Transactions . DeadlockCheckIntervalSec = 1
}
if cfg . Transactions . MaxSavepointsPerTx == 0 {
cfg . Transactions . MaxSavepointsPerTx = 100
}
if cfg . Transactions . CheckpointIntervalSec == 0 {
cfg . Transactions . CheckpointIntervalSec = 300
}
if cfg . ACL . MaxDeniedLogSize == 0 {
cfg . ACL . MaxDeniedLogSize = 10000
}
if cfg . ACL . TemporaryGrantTTLHours == 0 {
cfg . ACL . TemporaryGrantTTLHours = 24
}
if cfg . ACL . CacheTTLSec == 0 {
cfg . ACL . CacheTTLSec = 60
}
if cfg . ClusterTLS . MinVersion == "" {
cfg . ClusterTLS . MinVersion = "1.2"
}
if cfg . ClusterTLS . KeyRotationDays == 0 {
cfg . ClusterTLS . KeyRotationDays = 30
}
if cfg . Backpressure . CheckIntervalMs == 0 {
cfg . Backpressure . CheckIntervalMs = 1000
}
if cfg . Backpressure . LowDelayMs == 0 {
cfg . Backpressure . LowDelayMs = 100
}
if cfg . RuntimeLimits . GlobalMaxDocSizeMB == 0 {
cfg . RuntimeLimits . GlobalMaxDocSizeMB = 16
}
if cfg . RuntimeLimits . GlobalMaxCollSizeMB == 0 {
cfg . RuntimeLimits . GlobalMaxCollSizeMB = 10240
}
if cfg . RuntimeLimits . GlobalMaxDocsPerColl == 0 {
cfg . RuntimeLimits . GlobalMaxDocsPerColl = 10000000
}
if cfg . Autoscaling . MinNodes == 0 {
cfg . Autoscaling . MinNodes = 1
}
if cfg . Autoscaling . MaxNodes == 0 {
cfg . Autoscaling . MaxNodes = 10
}
if cfg . Autoscaling . ScaleUpCooldownSec == 0 {
cfg . Autoscaling . ScaleUpCooldownSec = 300
}
if cfg . Autoscaling . ScaleDownCooldownSec == 0 {
cfg . Autoscaling . ScaleDownCooldownSec = 600
}
if cfg . Autoscaling . EvaluationIntervalSec == 0 {
cfg . Autoscaling . EvaluationIntervalSec = 30
}
if cfg . SchemaMigration . MigrationDir == "" {
cfg . SchemaMigration . MigrationDir = "migrations"
}
if cfg . Backup . BackupDir == "" {
cfg . Backup . BackupDir = "backups"
}
if cfg . Backup . RetentionDays == 0 {
cfg . Backup . RetentionDays = 7
}
2026-09-21 22:10:41 +00:00
if ! cfg . Backup . IncludeSagaState {
cfg . Backup . IncludeSagaState = true
}
if ! cfg . Backup . EnableIncremental {
cfg . Backup . EnableIncremental = true
}
2026-09-20 21:50:21 +00:00
if cfg . Migration . Source == nil {
cfg . Migration . Source = & DatacenterConfig {
Name : "dc-primary" ,
Endpoint : "localhost:8080" ,
TimeoutSec : 30 ,
}
}
if cfg . Migration . Target == nil {
cfg . Migration . Target = & DatacenterConfig {
Name : "dc-secondary" ,
Endpoint : "localhost:8081" ,
TimeoutSec : 30 ,
}
}
if cfg . Migration . Settings == nil {
cfg . Migration . Settings = & MigrationSettings {
BatchSize : 1000 ,
Workers : 4 ,
Compression : "snappy" ,
ResumeEnabled : true ,
CheckpointIntervalSec : 30 ,
MaxRetries : 3 ,
RetryBackoffSec : 5 ,
}
}
if cfg . Migration . Delta == nil {
cfg . Migration . Delta = & DeltaSyncConfig {
Enabled : true ,
IntervalSec : 60 ,
MaxLagSec : 300 ,
}
}
if cfg . Migration . Validation == nil {
cfg . Migration . Validation = & ValidationConfig {
Enabled : true ,
SamplePercent : 10 ,
MaxErrors : 100 ,
}
}
if cfg . Migration . Mode == "" {
cfg . Migration . Mode = "semi_auto"
}
// ===== ИНИЦИАЛИЗАЦИЯ ДВИЖКОВ =====
if ! cfg . Engines . Row . Enabled {
cfg . Engines . Row . Enabled = true
cfg . Engines . Row . Description = "Row-based storage engine (default)"
}
if cfg . Engines . Row . Config == nil {
cfg . Engines . Row . Config = make ( map [ string ] interface {})
}
if cfg . Engines . Columnar . Config == nil {
cfg . Engines . Columnar . Config = make ( map [ string ] interface {})
}
if cfg . Engines . Document . Config == nil {
cfg . Engines . Document . Config = make ( map [ string ] interface {})
}
if cfg . Engines . KV . Config == nil {
cfg . Engines . KV . Config = make ( map [ string ] interface {})
}
if cfg . Engines . TS . Config == nil {
cfg . Engines . TS . Config = make ( map [ string ] interface {})
}
if cfg . Engines . Graph . Config == nil {
cfg . Engines . Graph . Config = make ( map [ string ] interface {})
}
// ===== ВАЛИДАЦИЯ КОНФИГУРАЦИИ =====
validationResult := ValidateConfigFull ( & cfg )
if ! validationResult . Valid {
var errMsg strings . Builder
errMsg . WriteString ( "config validation failed:\n" )
for _ , err := range validationResult . Errors {
errMsg . WriteString ( fmt . Sprintf ( " - %s\n" , err . Error ()))
}
if len ( validationResult . Warnings ) > 0 {
errMsg . WriteString ( "Warnings:\n" )
for _ , warn := range validationResult . Warnings {
errMsg . WriteString ( fmt . Sprintf ( " - %s\n" , warn . Error ()))
}
}
return nil , fmt . Errorf ( "%s" , errMsg . String ())
}
if len ( validationResult . Warnings ) > 0 {
fmt . Println ( "Configuration warnings:" )
for _ , warn := range validationResult . Warnings {
fmt . Printf ( " - %s\n" , warn . Error ())
}
}
return & cfg , nil
}
// ValidateConfig выполняет валидацию конфигурации и возвращает первую ошибку.
func ValidateConfig ( cfg * Config ) error {
if cfg == nil {
return fmt . Errorf ( "config is nil" )
}
result := ValidateConfigFull ( cfg )
if ! result . Valid {
if len ( result . Errors ) > 0 {
return result . Errors [ 0 ]
}
return fmt . Errorf ( "config validation failed" )
}
return nil
}
// ValidateConfigFull выполняет полную валидацию конфигурации.
func ValidateConfigFull ( cfg * Config ) * ValidationResult {
result := & ValidationResult {
Valid : true ,
Errors : make ([] error , 0 ),
Warnings : make ([] error , 0 ),
}
if cfg . Cluster . NodePort < 0 || cfg . Cluster . NodePort > 65535 {
result . Errors = append ( result . Errors , fmt . Errorf ( "invalid node_port: %d (must be 0-65535, 0 = auto)" , cfg . Cluster . NodePort ))
}
if cfg . Cluster . RaftPort <= 0 || cfg . Cluster . RaftPort > 65535 {
result . Errors = append ( result . Errors , fmt . Errorf ( "invalid raft_port: %d (must be 1-65535)" , cfg . Cluster . RaftPort ))
}
if cfg . Cluster . PriorityZone < 0 || cfg . Cluster . PriorityZone > 9 {
result . Errors = append ( result . Errors , fmt . Errorf ( "priority_zone must be between 0 and 9, got %d" , cfg . Cluster . PriorityZone ))
}
if cfg . Cluster . NodeIP != "" && cfg . Cluster . NodeIP != "0.0.0.0" && cfg . Cluster . NodeIP != "::" {
if net . ParseIP ( cfg . Cluster . NodeIP ) == nil {
result . Errors = append ( result . Errors , fmt . Errorf ( "invalid node_ip: %s (must be a valid IP address)" , cfg . Cluster . NodeIP ))
}
}
if cfg . Saga . Enabled {
if cfg . Saga . CoordinatorCount < 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "saga.coordinator_count must be at least 1, got %d" , cfg . Saga . CoordinatorCount ))
}
if cfg . Saga . CoordinatorCount < 3 {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "saga.coordinator_count is less than 3, may not be fully fault-tolerant" ))
}
if cfg . Saga . MaxRetries < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "saga.max_retries cannot be negative, got %d" , cfg . Saga . MaxRetries ))
}
if cfg . Saga . RetryBackoffMs < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "saga.retry_backoff_ms cannot be negative, got %d" , cfg . Saga . RetryBackoffMs ))
}
if cfg . Saga . SagaTimeoutSec < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "saga.saga_timeout_sec cannot be negative, got %d" , cfg . Saga . SagaTimeoutSec ))
}
if cfg . Saga . StuckCheckIntervalSec < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "saga.stuck_check_interval_sec cannot be negative, got %d" , cfg . Saga . StuckCheckIntervalSec ))
}
if cfg . Saga . AsyncRecoveryWorkers < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "saga.async_recovery_workers cannot be negative, got %d" , cfg . Saga . AsyncRecoveryWorkers ))
}
if cfg . Saga . ChannelBufferSize < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "saga.channel_buffer_size cannot be negative, got %d" , cfg . Saga . ChannelBufferSize ))
}
}
if cfg . Performance . BatchSize < 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "batch_size must be at least 1, got %d" , cfg . Performance . BatchSize ))
}
if cfg . Performance . MaxConnections < 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "max_connections must be at least 1, got %d" , cfg . Performance . MaxConnections ))
}
if cfg . Monitoring . TraceSampleRate < 0 || cfg . Monitoring . TraceSampleRate > 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "trace_sample_rate must be between 0 and 1, got %f" , cfg . Monitoring . TraceSampleRate ))
}
if cfg . Metrics . Enabled {
if cfg . Metrics . CollectIntervalSec <= 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "metrics.collect_interval_sec must be > 0 when metrics.enabled = true" ))
}
}
if cfg . Metrics . CollectIntervalSec < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "metrics.collect_interval_sec cannot be negative, got %d" , cfg . Metrics . CollectIntervalSec ))
}
if cfg . Log . LogLevel != "" {
level := strings . ToLower ( cfg . Log . LogLevel )
if ! allowedLogLevels [ level ] {
result . Errors = append ( result . Errors , fmt . Errorf ( "invalid log_level: %s (allowed: debug, info, warn, error, fatal)" , cfg . Log . LogLevel ))
}
}
if cfg . API . Port == cfg . Cluster . NodePort {
result . Errors = append ( result . Errors , fmt . Errorf ( "API port %d conflicts with cluster node port" , cfg . API . Port ))
}
if cfg . Monitoring . MetricsPort == cfg . Cluster . NodePort {
result . Errors = append ( result . Errors , fmt . Errorf ( "metrics port %d conflicts with cluster node port" , cfg . Monitoring . MetricsPort ))
}
if cfg . API . Port == cfg . Monitoring . MetricsPort {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "API port %d conflicts with metrics port" , cfg . API . Port ))
}
if cfg . Security . IsTLSEnabled () {
if cfg . Security . CertFile == "" {
result . Errors = append ( result . Errors , fmt . Errorf ( "cert_file is required when TLS is enabled" ))
}
if cfg . Security . KeyFile == "" {
result . Errors = append ( result . Errors , fmt . Errorf ( "key_file is required when TLS is enabled" ))
}
}
if cfg . Compression . Enabled {
if ! allowedCompressionAlgorithms [ cfg . Compression . Algorithm ] {
result . Errors = append ( result . Errors , fmt . Errorf ( "unsupported compression algorithm: %s, supported: snappy, lz4, zstd" , cfg . Compression . Algorithm ))
}
}
if cfg . WAL . RecoveryWorkers < 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "wal.recovery_workers must be at least 1, got %d" , cfg . WAL . RecoveryWorkers ))
}
if cfg . WAL . AsyncRecoveryWorkers < 1 && cfg . WAL . AsyncRecovery {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "wal.async_recovery_workers is less than 1, will use default 4" ))
}
if cfg . WAL . AsyncRecoveryBuffer < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "wal.async_recovery_buffer cannot be negative, got %d" , cfg . WAL . AsyncRecoveryBuffer ))
}
if cfg . MVCC . MaxVersionsPerDoc < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "mvcc.max_versions_per_doc cannot be negative, got %d" , cfg . MVCC . MaxVersionsPerDoc ))
}
if cfg . Plugins . MaxCPUTimeMs < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "plugins.max_cpu_time_ms cannot be negative, got %d" , cfg . Plugins . MaxCPUTimeMs ))
}
if cfg . Plugins . MaxMemoryMB < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "plugins.max_memory_mb cannot be negative, got %d" , cfg . Plugins . MaxMemoryMB ))
}
if cfg . Plugins . MaxLuaStates < 1 {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "plugins.max_lua_states is less than 1, will use default 100" ))
}
if cfg . Backpressure . Enabled {
if cfg . Backpressure . CPUThreshold < 0 || cfg . Backpressure . CPUThreshold > 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "backpressure.cpu_threshold must be between 0 and 1, got %f" , cfg . Backpressure . CPUThreshold ))
}
if cfg . Backpressure . MemoryThreshold < 0 || cfg . Backpressure . MemoryThreshold > 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "backpressure.memory_threshold must be between 0 and 1, got %f" , cfg . Backpressure . MemoryThreshold ))
}
if cfg . Backpressure . MediumRejectProb > 100 {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "backpressure.medium_reject_prob is > 100, will be capped to 100" ))
}
if cfg . Backpressure . HighRejectProb > 100 {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "backpressure.high_reject_prob is > 100, will be capped to 100" ))
}
}
if cfg . RuntimeLimits . Enabled {
if cfg . RuntimeLimits . GlobalMaxDocSizeMB < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "runtime_limits.global_max_doc_size_mb cannot be negative, got %d" , cfg . RuntimeLimits . GlobalMaxDocSizeMB ))
}
if cfg . RuntimeLimits . GlobalMaxCollSizeMB < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "runtime_limits.global_max_coll_size_mb cannot be negative, got %d" , cfg . RuntimeLimits . GlobalMaxCollSizeMB ))
}
if cfg . RuntimeLimits . GlobalMaxDocsPerColl < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "runtime_limits.global_max_docs_per_coll cannot be negative, got %d" , cfg . RuntimeLimits . GlobalMaxDocsPerColl ))
}
}
if cfg . Autoscaling . Enabled {
if cfg . Autoscaling . MinNodes < 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "autoscaling.min_nodes must be at least 1, got %d" , cfg . Autoscaling . MinNodes ))
}
if cfg . Autoscaling . MaxNodes < cfg . Autoscaling . MinNodes {
result . Errors = append ( result . Errors , fmt . Errorf ( "autoscaling.max_nodes (%d) must be >= min_nodes (%d)" , cfg . Autoscaling . MaxNodes , cfg . Autoscaling . MinNodes ))
}
if cfg . Autoscaling . ScaleUpThreshold <= 0 || cfg . Autoscaling . ScaleUpThreshold > 1 {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "autoscaling.scale_up_threshold is invalid, will use default 0.75" ))
}
if cfg . Autoscaling . ScaleDownThreshold <= 0 || cfg . Autoscaling . ScaleDownThreshold >= cfg . Autoscaling . ScaleUpThreshold {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "autoscaling.scale_down_threshold should be less than scale_up_threshold" ))
}
}
if cfg . Storage . EnableCustomEngines {
defaultEngine := cfg . Storage . GetDefaultEngine ()
switch defaultEngine {
case "row" , "columnar" , "document" , "kv" , "ts" , "graph" :
default :
result . Warnings = append ( result . Warnings , fmt . Errorf ( "default_engine '%s' is not a built-in engine, ensure plugin is loaded" , defaultEngine ))
}
}
2026-09-21 22:10:41 +00:00
if cfg . Storage . EnableCustomEngines {
anyEnabled := cfg . Engines . Row . Enabled ||
cfg . Engines . Columnar . Enabled ||
cfg . Engines . Document . Enabled ||
cfg . Engines . KV . Enabled ||
cfg . Engines . TS . Enabled ||
cfg . Engines . Graph . Enabled
if ! anyEnabled {
result . Errors = append ( result . Errors , fmt . Errorf ( "at least one engine must be enabled in [engines]" ))
}
}
if cfg . Backup . Enabled {
if cfg . Backup . BackupDir == "" {
result . Errors = append ( result . Errors , fmt . Errorf ( "backup.backup_dir is required when backup.enabled = true" ))
}
if cfg . Backup . RetentionDays < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "backup.retention_days cannot be negative, got %d" , cfg . Backup . RetentionDays ))
}
if cfg . Backup . MaxConcurrent < 1 {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "backup.max_concurrent < 1, will use default 1" ))
}
}
2026-09-20 21:50:21 +00:00
if cfg . Migration . Enabled {
if cfg . Migration . Source == nil || cfg . Migration . Source . Endpoint == "" {
result . Errors = append ( result . Errors , fmt . Errorf ( "migration.source.endpoint is required when migration is enabled" ))
}
if cfg . Migration . Target == nil || cfg . Migration . Target . Endpoint == "" {
result . Errors = append ( result . Errors , fmt . Errorf ( "migration.target.endpoint is required when migration is enabled" ))
}
if cfg . Migration . Settings == nil {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "migration.settings not configured, using defaults" ))
}
if cfg . Migration . Delta == nil {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "migration.delta not configured, using defaults" ))
}
if cfg . Migration . Validation == nil {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "migration.validation not configured, using defaults" ))
}
if ! allowedMigrationModes [ cfg . Migration . Mode ] {
result . Errors = append ( result . Errors , fmt . Errorf ( "migration.mode must be one of: manual, semi_auto, auto, got %s" , cfg . Migration . Mode ))
}
if cfg . Migration . Settings != nil && cfg . Migration . Settings . BatchSize < 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "migration.settings.batch_size must be at least 1, got %d" , cfg . Migration . Settings . BatchSize ))
}
if cfg . Migration . Settings != nil && cfg . Migration . Settings . Workers < 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "migration.settings.workers must be at least 1, got %d" , cfg . Migration . Settings . Workers ))
}
if cfg . Migration . Settings != nil && cfg . Migration . Settings . MaxRetries < 0 {
result . Errors = append ( result . Errors , fmt . Errorf ( "migration.settings.max_retries cannot be negative, got %d" , cfg . Migration . Settings . MaxRetries ))
}
if cfg . Migration . Validation != nil && ( cfg . Migration . Validation . SamplePercent < 0 || cfg . Migration . Validation . SamplePercent > 100 ) {
result . Errors = append ( result . Errors , fmt . Errorf ( "migration.validation.sample_percent must be between 0 and 100, got %d" , cfg . Migration . Validation . SamplePercent ))
}
if cfg . Migration . Delta != nil && cfg . Migration . Delta . IntervalSec < 1 {
result . Errors = append ( result . Errors , fmt . Errorf ( "migration.delta.interval_sec must be at least 1, got %d" , cfg . Migration . Delta . IntervalSec ))
}
if cfg . Migration . Source != nil && cfg . Migration . Source . APIKey != "" {
if len ( cfg . Migration . Source . APIKey ) < minAPIKeyLength {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "migration.source.api_key is shorter than %d characters" , minAPIKeyLength ))
}
}
if cfg . Migration . Target != nil && cfg . Migration . Target . APIKey != "" {
if len ( cfg . Migration . Target . APIKey ) < minAPIKeyLength {
result . Warnings = append ( result . Warnings , fmt . Errorf ( "migration.target.api_key is shorter than %d characters" , minAPIKeyLength ))
}
}
}
result . Valid = len ( result . Errors ) == 0
return result
2026-10-05 21:26:41 +00:00
}
// =============================================================================
// СОХРАНЕНИЕ КОНФИГУРАЦИИ (ДОБАВЛЕНО)
// =============================================================================
// SaveConfig записывает конфигурацию в TOML-файл атомарно:
// сначала во временный файл (с fsync), потом rename.
//
// Используется REPL-командой "config set" в fallback-режиме, когда
// DynamicConfigManager недоступен (Raft выключен или узел не лидер).
//
// Если path == "" — возвращает ошибку.
// Если cfg == nil — возвращает ошибку.
//
// После успешного rename выполняется fsync родительской директории,
// чтобы гарантировать сохранность переименования при крахе.
func SaveConfig ( path string , cfg * Config ) error {
if cfg == nil {
return fmt . Errorf ( "config is nil" )
}
if path == "" {
return fmt . Errorf ( "config path is empty" )
}
dir := filepath . Dir ( path )
if err := os . MkdirAll ( dir , 0755 ); err != nil {
return fmt . Errorf ( "failed to create config directory %s: %w" , dir , err )
}
tmpPath := path + ".tmp"
f , err := os . OpenFile ( tmpPath , os . O_CREATE | os . O_WRONLY | os . O_TRUNC , 0644 )
if err != nil {
return fmt . Errorf ( "failed to create temp config file: %w" , err )
}
encoder := toml . NewEncoder ( f )
if err := encoder . Encode ( cfg ); err != nil {
f . Close ()
os . Remove ( tmpPath )
return fmt . Errorf ( "failed to encode config: %w" , err )
}
if err := f . Sync (); err != nil {
f . Close ()
os . Remove ( tmpPath )
return fmt . Errorf ( "failed to sync config: %w" , err )
}
if err := f . Close (); err != nil {
os . Remove ( tmpPath )
return fmt . Errorf ( "failed to close config: %w" , err )
}
if err := os . Rename ( tmpPath , path ); err != nil {
os . Remove ( tmpPath )
return fmt . Errorf ( "failed to rename config: %w" , err )
}
// fsync директории — best-effort, на Windows no-op.
if err := fsyncDir ( dir ); err != nil {
// Не критично: файл уже синхронизирован, rename выполнен.
_ = err
}
return nil
}
// fsyncDir синхронизирует директорию.
// На Linux/macOS/BSD это работает; на Windows — no-op.
func fsyncDir ( dir string ) error {
d , err := os . Open ( dir )
if err != nil {
return err
}
defer d . Close ()
return d . Sync ()
2026-09-21 22:10:41 +00:00
}