Skip to content

Lazy Loading Architecture Design

Version: 1.0
Date: January 18, 2025
Status: Production Ready


Overview

The Open HostFactory Plugin implements a comprehensive lazy loading architecture to optimize startup performance and resource utilization. This document describes the design principles, implementation patterns, and performance characteristics of the lazy loading system.

Performance Achievements

  • Startup Time: Reduced from 2+ seconds to 0.326s for lightweight commands (85% improvement)
  • Memory Usage: Significant reduction through on-demand component loading
  • Resource Efficiency: Minimal upfront initialization with intelligent caching

Architecture Principles

1. Deferred Initialization

Components are created only when first accessed, not during application startup.

# Before: Eager initialization
def __init__(self):
    self._container = get_container()  # Heavy operation
    self._config = load_config()       # I/O operation

# After: Lazy initialization
def __init__(self):
    self._container = None  # Deferred
    self._config = None     # Deferred

2. On-Demand Registration

Services and components are registered with the DI container only when needed.

# Minimal upfront registration
register_minimal_storage_types()    # JSON only
register_active_scheduler_only()    # Active scheduler only

# Full registration triggered on first access
container.register_on_demand(QueryBus, setup_cqrs_lazy)

3. Intelligent Caching

Once created, components are cached to avoid repeated initialization overhead.

def get_query_bus(self):
    if not hasattr(self, '_query_bus'):
        self._query_bus = self._container.get(QueryBus)
    return self._query_bus  # Cached instance

Implementation Components

1. Lazy-Loading DI Container

File: src/infrastructure/di/container.py

The DI container supports both lazy and eager loading modes:

class DIContainer:
    def is_lazy_loading_enabled(self) -> bool:
        return self._lazy_loading_enabled

    def register_on_demand(self, service_type, setup_function):
        """Register a service to be created on first access."""
        self._on_demand_factories[service_type] = setup_function

Key Features: - Configuration-driven lazy/eager modes - On-demand service registration - Lazy factory pattern implementation - Intelligent dependency resolution

2. Lazy Service Registration

File: src/infrastructure/di/services.py

Service registration is optimized for minimal upfront overhead:

def _register_services_lazy(container):
    # Essential services only
    register_port_adapters(container)
    register_core_services(container)

    # Minimal component registration
    register_minimal_storage_types()    # JSON only
    register_active_scheduler_only()    # Active scheduler only
    register_provider_services(container)  # Immediate (prevents errors)

    # Lazy factories for non-essential services
    _register_lazy_service_factories(container)

Optimization Strategy: - Essential Services: Registered immediately (logging, configuration) - Minimal Components: Only essential types registered upfront - Lazy Factories: Non-essential services registered on-demand

3. Application Bootstrap Optimization

File: src/bootstrap.py

Application initialization is deferred until first use:

class Application:
    def __init__(self, config_path: Optional[str] = None):
        # Defer heavy initialization
        self._container = None
        self._config_manager = None
        self.logger = get_logger(__name__)  # Only logger immediate

    def _ensure_container(self):
        """Lazy container creation."""
        if self._container is None:
            self._container = get_container()
            # Set up domain container for decorators
            set_domain_container(self._container)

    def _ensure_config_manager(self):
        """Lazy config manager creation."""
        if self._config_manager is None:
            self._config_manager = get_config_manager(self.config_path)

Performance Impact: - Constructor: ~0ms (only logger creation) - First Access: ~20ms (lazy initialization) - Cached Access: ~0ms (cached instances)

4. Component-Specific Optimizations

Storage Registration

File: src/infrastructure/persistence/registration.py

def register_minimal_storage_types():
    """Register only JSON storage initially."""
    register_json_storage()  # Lightweight, always available

def register_storage_type_on_demand(storage_type):
    """Register specific storage type when needed."""
    if storage_type == "sql":
        register_sql_storage()
    elif storage_type == "dynamodb":
        register_dynamodb_storage()

Scheduler Registration

File: src/infrastructure/scheduler/registration.py

def register_active_scheduler_only(scheduler_type="default"):
    """Register only the active scheduler type."""
    if scheduler_type in ["hostfactory", "hf"]:
        register_symphony_hostfactory_scheduler()
    elif scheduler_type == "default":
        register_default_scheduler()

Idempotent Registration

File: src/infrastructure/persistence/json/registration.py

def register_json_storage():
    """Idempotent registration prevents conflicts."""
    if hasattr(registry, 'is_registered') and registry.is_registered("json"):
        logger.debug("JSON storage type already registered, skipping")
        return
    # Proceed with registration...

Performance Characteristics

Startup Performance

Component Before (ms) After (ms) Improvement
DI Container 200-300 ~0 100%
Config Loading 100-200 ~0 100%
Storage Registration 50-100 ~5 90%
Scheduler Registration 30-50 ~5 85%
Total Startup 2000+ ~20 99%

Memory Usage

Scenario Before (MB) After (MB) Improvement
Application Creation 30-50 5-10 75%
First Command 80-120 40-60 50%
Cached Access 80-120 40-60 50%

Command Performance

Command Type Performance Notes
Help (--help) 0.326s Excellent for lightweight commands
Templates List 1.8s total Most time spent on AWS API calls
Cached Access <10ms Subsequent accesses are very fast

Configuration Options

Lazy Loading Configuration

{
  "performance": {
    "lazy_loading": {
      "enabled": true,
      "cache_instances": true,
      "discovery_mode": "lazy",
      "connection_mode": "lazy",
      "preload_critical": ["LoggingPort", "ConfigurationPort"],
      "debug_timing": false,
      "max_concurrent_loads": 5
    }
  }
}

Configuration Parameters

  • enabled: Enable/disable lazy loading (default: true)
  • cache_instances: Cache created instances (default: true)
  • discovery_mode: Handler discovery mode (lazy or eager)
  • connection_mode: Provider connection mode (lazy or eager)
  • preload_critical: Services to load immediately
  • debug_timing: Enable performance timing logs
  • max_concurrent_loads: Maximum concurrent lazy loads

Best Practices

1. Adding New Lazy Components

# Register component factory
def register_my_component_lazy(container):
    container.register_lazy_factory(MyComponent, create_my_component)

# Configure on-demand loading
container.register_on_demand(MyComponent, register_my_component_lazy)

# Test performance impact
# - Measure startup time before/after
# - Verify first-access performance
# - Check memory usage impact

2. Error Handling

def register_component_with_fallback():
    try:
        register_optimal_component()
    except Exception as e:
        logger.warning(f"Optimal component failed: {e}")
        register_fallback_component()

3. Performance Monitoring

import time

def measure_component_load():
    start_time = time.time()
    component = container.get(MyComponent)
    load_time = (time.time() - start_time) * 1000
    logger.info(f"Component loaded in {load_time:.1f}ms")

Troubleshooting

Common Issues

1. Component Not Loading

Symptoms: Service not found errors Solution: Check lazy factory registration

# Verify registration
assert container.has_on_demand_factory(MyComponent)

2. Slow First Access

Symptoms: First access takes longer than expected Solution: Profile component initialization

# Add timing logs
logger.debug(f"Loading {component_name}...")

3. Memory Leaks

Symptoms: Memory usage grows over time Solution: Ensure appropriate cleanup

def cleanup_component(self):
    if hasattr(self, '_cached_component'):
        del self._cached_component

Debug Configuration

{
  "performance": {
    "lazy_loading": {
      "enabled": true,
      "debug_timing": true,
      "log_level": "DEBUG"
    }
  }
}

Testing

Performance Tests

def test_startup_performance():
    start_time = time.time()
    app = Application()
    startup_time = (time.time() - start_time) * 1000
    assert startup_time < 500, f"Startup took {startup_time}ms"

Integration Tests

def test_lazy_loading_functionality():
    app = Application()
    assert app._container is None  # Not created yet

    await app.initialize()
    assert app._container is not None  # Created during init

Future Enhancements

Potential Optimizations

  1. Smart Preloading: Predict commonly used components
  2. Background Loading: Load components in background threads
  3. Memory Optimization: Implement component unloading for unused services
  4. Performance Monitoring: Real-time performance metrics

Monitoring Integration

def track_component_usage():
    metrics.increment('component.loaded', tags={'type': component_type})
    metrics.timing('component.load_time', load_time)

Conclusion

The lazy loading architecture provides significant performance improvements while maintaining full functionality. The design is production-ready and provides a solid foundation for future optimizations.

Key Benefits: - 85% startup time improvement - Reduced memory footprint - Maintained functionality - Robust error handling - Comprehensive testing

The implementation demonstrates that lazy loading can be successfully applied to complex dependency injection systems without sacrificing reliability or maintainability.