221 Zeilen
8.7 KiB
Python
221 Zeilen
8.7 KiB
Python
"""
|
|
Adaptive Rate Limit Use Case - Passt Geschwindigkeit dynamisch an
|
|
"""
|
|
|
|
import logging
|
|
from typing import Dict, Any, Optional
|
|
from datetime import datetime, timedelta
|
|
|
|
from domain.services.rate_limit_service import IRateLimitService
|
|
from domain.value_objects.action_timing import ActionTiming, ActionType
|
|
from domain.entities.rate_limit_policy import RateLimitPolicy
|
|
|
|
logger = logging.getLogger("adaptive_rate_limit_use_case")
|
|
|
|
|
|
class AdaptiveRateLimitUseCase:
|
|
"""
|
|
Use Case für adaptive Geschwindigkeitsanpassung basierend auf Systemverhalten.
|
|
Analysiert Response-Zeiten, passt Delays dynamisch an und erkennt Anomalien.
|
|
"""
|
|
|
|
def __init__(self, rate_limit_service: IRateLimitService):
|
|
self.rate_limit_service = rate_limit_service
|
|
self.anomaly_threshold = 2.0 # Standardabweichungen für Anomalie
|
|
self.adaptation_interval = timedelta(minutes=5)
|
|
self.last_adaptation = {}
|
|
|
|
def execute(self, action_type: ActionType, context: Optional[Dict[str, Any]] = None) -> float:
|
|
"""
|
|
Führt adaptive Rate Limiting Logik aus.
|
|
|
|
Args:
|
|
action_type: Typ der auszuführenden Aktion
|
|
context: Zusätzlicher Kontext (z.B. Session-ID, Platform)
|
|
|
|
Returns:
|
|
Optimale Verzögerung in Sekunden
|
|
"""
|
|
# Prüfe ob Adaptation notwendig ist
|
|
if self._should_adapt(action_type):
|
|
self._adapt_policy(action_type)
|
|
|
|
# Berechne Delay mit aktuellem Policy
|
|
delay = self.rate_limit_service.calculate_delay(action_type, context)
|
|
|
|
# Warte wenn nötig
|
|
actual_wait = self.rate_limit_service.wait_if_needed(action_type)
|
|
|
|
logger.debug(f"Adaptive delay for {action_type.value}: {delay:.2f}s (waited: {actual_wait:.2f}s)")
|
|
|
|
return delay
|
|
|
|
def record_timing(self, timing: ActionTiming) -> None:
|
|
"""
|
|
Zeichnet Timing auf und triggert ggf. Anpassungen.
|
|
|
|
Args:
|
|
timing: Timing-Informationen der ausgeführten Aktion
|
|
"""
|
|
# Zeichne Timing auf
|
|
self.rate_limit_service.record_action(timing)
|
|
|
|
# Analysiere auf Anomalien
|
|
if self._is_anomaly(timing):
|
|
logger.warning(f"Anomaly detected for {timing.action_type.value}: "
|
|
f"duration={timing.duration}s, success={timing.success}")
|
|
self._handle_anomaly(timing)
|
|
|
|
def _should_adapt(self, action_type: ActionType) -> bool:
|
|
"""Prüft ob Policy angepasst werden sollte"""
|
|
last = self.last_adaptation.get(action_type, datetime.min)
|
|
return datetime.now() - last > self.adaptation_interval
|
|
|
|
def _adapt_policy(self, action_type: ActionType) -> None:
|
|
"""Passt Policy basierend auf gesammelten Daten an"""
|
|
# Hole Statistiken
|
|
stats = self.rate_limit_service.get_statistics(
|
|
action_type,
|
|
timeframe=timedelta(hours=1)
|
|
)
|
|
|
|
if not stats or 'success_rate' not in stats:
|
|
return
|
|
|
|
current_policy = self.rate_limit_service.get_policy(action_type)
|
|
success_rate = stats['success_rate']
|
|
avg_duration = stats.get('avg_duration_ms', 0) / 1000.0
|
|
|
|
# Neue Policy-Parameter berechnen
|
|
new_policy = self._calculate_new_policy(
|
|
current_policy,
|
|
success_rate,
|
|
avg_duration
|
|
)
|
|
|
|
if new_policy != current_policy:
|
|
self.rate_limit_service.update_policy(action_type, new_policy)
|
|
logger.info(f"Adapted policy for {action_type.value}: "
|
|
f"min_delay={new_policy.min_delay:.2f}, "
|
|
f"max_delay={new_policy.max_delay:.2f}")
|
|
|
|
self.last_adaptation[action_type] = datetime.now()
|
|
|
|
def _calculate_new_policy(self, current: RateLimitPolicy,
|
|
success_rate: float,
|
|
avg_duration: float) -> RateLimitPolicy:
|
|
"""Berechnet neue Policy-Parameter"""
|
|
# Kopiere aktuelle Policy
|
|
new_min = current.min_delay
|
|
new_max = current.max_delay
|
|
new_backoff = current.backoff_multiplier
|
|
|
|
# Anpassung basierend auf Erfolgsrate
|
|
if success_rate < 0.7: # Niedrige Erfolgsrate
|
|
# Erhöhe Delays signifikant
|
|
new_min = min(new_min * 1.3, 10.0)
|
|
new_max = min(new_max * 1.3, 30.0)
|
|
new_backoff = min(new_backoff * 1.1, 3.0)
|
|
elif success_rate < 0.85: # Mittlere Erfolgsrate
|
|
# Moderate Erhöhung
|
|
new_min = min(new_min * 1.1, 10.0)
|
|
new_max = min(new_max * 1.1, 30.0)
|
|
elif success_rate > 0.95: # Hohe Erfolgsrate
|
|
# Vorsichtige Verringerung
|
|
if avg_duration < current.min_delay * 0.8:
|
|
new_min = max(new_min * 0.9, 0.1)
|
|
new_max = max(new_max * 0.9, new_min * 3)
|
|
|
|
# Stelle sicher dass max > min
|
|
new_max = max(new_max, new_min * 2)
|
|
|
|
return RateLimitPolicy(
|
|
min_delay=round(new_min, 2),
|
|
max_delay=round(new_max, 2),
|
|
adaptive=current.adaptive,
|
|
backoff_multiplier=round(new_backoff, 2),
|
|
max_retries=current.max_retries
|
|
)
|
|
|
|
def _is_anomaly(self, timing: ActionTiming) -> bool:
|
|
"""Erkennt ob ein Timing eine Anomalie darstellt"""
|
|
# Hole Statistiken für Vergleich
|
|
stats = self.rate_limit_service.get_statistics(
|
|
timing.action_type,
|
|
timeframe=timedelta(hours=1)
|
|
)
|
|
|
|
if not stats or 'avg_duration_ms' not in stats:
|
|
return False
|
|
|
|
avg_duration = stats['avg_duration_ms'] / 1000.0
|
|
|
|
# Sehr langsame Requests sind Anomalien
|
|
if timing.duration > avg_duration * self.anomaly_threshold:
|
|
return True
|
|
|
|
# Fehler nach mehreren Erfolgen sind Anomalien
|
|
if not timing.success and stats.get('success_rate', 0) > 0.9:
|
|
return True
|
|
|
|
return False
|
|
|
|
def _handle_anomaly(self, timing: ActionTiming) -> None:
|
|
"""Behandelt erkannte Anomalien"""
|
|
# Sofortige Policy-Anpassung bei kritischen Anomalien
|
|
if not timing.success and timing.error_message:
|
|
if any(indicator in timing.error_message.lower()
|
|
for indicator in ['rate limit', 'too many', 'blocked']):
|
|
# Rate Limit erkannt - sofort anpassen
|
|
current_policy = self.rate_limit_service.get_policy(timing.action_type)
|
|
emergency_policy = RateLimitPolicy(
|
|
min_delay=min(current_policy.min_delay * 2, 10.0),
|
|
max_delay=min(current_policy.max_delay * 2, 30.0),
|
|
adaptive=current_policy.adaptive,
|
|
backoff_multiplier=min(current_policy.backoff_multiplier * 1.5, 3.0),
|
|
max_retries=current_policy.max_retries
|
|
)
|
|
self.rate_limit_service.update_policy(timing.action_type, emergency_policy)
|
|
logger.warning(f"Emergency policy update for {timing.action_type.value} due to rate limit")
|
|
|
|
def get_recommendations(self) -> Dict[str, Any]:
|
|
"""Gibt Empfehlungen basierend auf aktuellen Metriken"""
|
|
recommendations = {
|
|
'actions': [],
|
|
'warnings': [],
|
|
'optimizations': []
|
|
}
|
|
|
|
# Analysiere alle Action Types
|
|
for action_type in ActionType:
|
|
stats = self.rate_limit_service.get_statistics(
|
|
action_type,
|
|
timeframe=timedelta(hours=24)
|
|
)
|
|
|
|
if not stats or stats.get('total_actions', 0) < 10:
|
|
continue
|
|
|
|
success_rate = stats.get('success_rate', 0)
|
|
avg_retries = stats.get('avg_retry_count', 0)
|
|
|
|
# Empfehlungen basierend auf Metriken
|
|
if success_rate < 0.5:
|
|
recommendations['warnings'].append(
|
|
f"{action_type.value}: Sehr niedrige Erfolgsrate ({success_rate:.1%})"
|
|
)
|
|
recommendations['actions'].append(
|
|
f"Erhöhe Delays für {action_type.value} oder prüfe auf Blocking"
|
|
)
|
|
|
|
if avg_retries > 2:
|
|
recommendations['warnings'].append(
|
|
f"{action_type.value}: Hohe Retry-Rate ({avg_retries:.1f})"
|
|
)
|
|
|
|
if success_rate > 0.98 and stats.get('avg_duration_ms', 0) < 500:
|
|
recommendations['optimizations'].append(
|
|
f"{action_type.value}: Könnte schneller ausgeführt werden"
|
|
)
|
|
|
|
return recommendations |