Migrating Legacy PHP Applications to Laravel Octane: A Deep Dive into Performance Gains and Architectural Shifts
Understanding Laravel Octane’s Core: Beyond Traditional PHP Request Cycles
Migrating a legacy PHP application to Laravel Octane isn’t merely an upgrade; it’s a fundamental architectural shift. Traditional PHP applications, even those using frameworks like older Laravel versions, operate on a per-request basis. This means the entire PHP process is spun up, bootstrapped, dependencies are loaded, the request is processed, and the response is sent. Then, the process is torn down, only to be reborn for the next incoming request. This overhead, while familiar, is a significant performance bottleneck, especially under high load.
Laravel Octane, powered by Swoole or RoadRunner, fundamentally changes this paradigm by introducing long-running application servers. Instead of a new PHP process for each request, Octane keeps your application in memory. This means the bootstrapping, dependency injection container setup, and middleware initialization happen only once when the server starts. Subsequent requests are processed within this persistent environment, drastically reducing latency and resource consumption.
Identifying Candidates for Octane Migration
Not all legacy PHP applications are immediate, perfect fits for Octane. The primary benefit of Octane lies in its ability to serve many requests from a single, long-running process. This makes it ideal for applications that are:
- API-centric: Stateless APIs that don’t rely on persistent user sessions or complex server-side state management are prime candidates.
- High-traffic web applications: Applications experiencing significant request volumes will see the most dramatic performance improvements.
- Real-time applications: Octane’s underlying servers (Swoole/RoadRunner) offer built-in support for WebSockets, making it excellent for chat applications, live dashboards, and notifications.
- Background job processors: While not the primary use case, Octane can manage long-running background tasks more efficiently than traditional cron jobs or queue workers.
Conversely, applications with:
- Heavy reliance on global state or singletons that are not thread-safe: These can lead to unexpected behavior and data corruption in a long-running process.
- Frequent, complex database schema changes or application code deployments without proper server restarts: Octane requires a restart to pick up code changes, unlike traditional PHP-FPM which can often reload configurations more dynamically.
- Strict adherence to the traditional CGI/FastCGI request lifecycle for specific functionalities: Some older libraries or custom integrations might have implicit assumptions about process isolation per request.
Pre-Migration Assessment: State Management and Side Effects
The most critical aspect of migrating to Octane is understanding and mitigating potential side effects caused by the persistent nature of the application server. In a traditional PHP-FPM setup, each request is a clean slate. Global variables, static properties, and cached data are reset or managed within the scope of a single request. With Octane, these can persist across requests, leading to bugs if not handled carefully.
Key areas to scrutinize:
- Global Variables and Static Properties: Any global variables or static properties that are modified during a request must be reset at the end of that request. This is often the most common source of bugs.
- In-memory Caches: If your application uses custom in-memory caching mechanisms (e.g., static arrays, singleton objects holding data), ensure they are either cleared between requests or designed to be thread-safe and handle concurrent access.
- Database Connections: While Laravel’s Eloquent handles connection pooling reasonably well, be mindful of any custom connection management that might hold state.
- File Handles and Resources: Ensure that any opened file handles, network sockets, or other resources are properly closed at the end of each request.
- Third-Party Libraries: Some older or poorly written libraries might have internal state that is not designed for a long-running process. Thorough testing is crucial.
Step-by-Step Migration Process
The migration can be approached incrementally. It’s highly recommended to set up a staging environment that mirrors your production setup as closely as possible.
1. Install Laravel Octane
First, add Octane to your project via Composer:
composer require laravel/octane
2. Choose Your Application Server
Octane supports Swoole and RoadRunner. Swoole is generally easier to get started with for PHP developers, while RoadRunner offers more advanced features and configuration options, often preferred in more complex microservice architectures.
For Swoole:
php artisan octane:install --swoole
This command publishes the config/octane.php file and sets up the necessary configuration for Swoole. You’ll also need to ensure the Swoole PHP extension is installed on your server. For Ubuntu/Debian:
pecl install swoole echo "extension=swoole.so" >> /etc/php/[php_version]/mods-available/swoole.ini phpenmod swoole
Replace [php_version] with your PHP version (e.g., 8.1).
For RoadRunner:
composer require spiral/roadrunner-cli --dev php artisan octane:install --roadrunner
This will publish config/octane.php and rr.yaml. RoadRunner is a standalone binary. You’ll need to download the appropriate binary for your operating system from the RoadRunner releases page and place it in your project’s root or a designated binary directory.
3. Configure Octane
The config/octane.php file is your primary configuration point. Key settings include:
return [
/*
|--------------------------------------------------------------------------
| Default server process manager
|--------------------------------------------------------------------------
|
| This option controls the default server process manager that will be used
| by Octane. The available options are: "swoole", "roadrunner", and "frankenphp".
|
*/
'server' => env('OCTANE_SERVER', 'swoole'),
/*
|--------------------------------------------------------------------------
| Warm the application during bootstrapping.
|--------------------------------------------------------------------------
|
| This option controls whether Octane should warm the application during
| bootstrapping. This means that the application's service providers will
| be loaded and cached before the server starts accepting requests.
|
*/
'warm' => env('OCTANE_WARM', true),
/*
|--------------------------------------------------------------------------
| The number of seconds the server should remain idle before exiting.
|--------------------------------------------------------------------------
|
| This option controls the maximum number of seconds the server should
| remain idle before exiting. This is useful for keeping your server
| running efficiently by automatically shutting down idle processes.
|
*/
'max_requests' => env('OCTANE_MAX_REQUESTS', 500),
/*
|--------------------------------------------------------------------------
| The maximum number of seconds a request should take to complete.
|--------------------------------------------------------------------------
|
| This option controls the maximum number of seconds a request should take
| to complete. If a request exceeds this limit, it will be terminated and
| an error will be logged. This is a crucial safety mechanism for preventing
| runaway processes.
|
*/
'timeout' => env('OCTANE_TIMEOUT', 30),
/*
|--------------------------------------------------------------------------
| The number of seconds to wait for the server to shut down gracefully.
|--------------------------------------------------------------------------
|
| This option controls the number of seconds Octane will wait for the
| server to shut down gracefully when the application is stopped.
|
*/
'graceful_shutdown_wait' => env('OCTANE_GRACEFUL_SHUTDOWN_WAIT', 5),
/*
|--------------------------------------------------------------------------
| The cache driver that Octane should use for caching application data.
|--------------------------------------------------------------------------
|
| This option controls the cache driver that Octane should use for caching
| application data. The available options are: "file", "redis", "memcached",
| and "database".
|
*/
'cache' => env('OCTANE_CACHE', 'file'),
/*
|--------------------------------------------------------------------------
| The cache duration in seconds for application data.
|--------------------------------------------------------------------------
|
| This option controls the cache duration in seconds for application data.
| A longer duration means less frequent cache invalidation, but potentially
| stale data.
|
*/
'cache_duration' => env('OCTANE_CACHE_DURATION', 100),
/*
|--------------------------------------------------------------------------
| The number of worker processes to start.
|--------------------------------------------------------------------------
|
| This option controls the number of worker processes to start. A higher
| number can improve concurrency but may also increase resource usage.
|
*/
'workers' => env('OCTANE_WORKERS', 4),
/*
|--------------------------------------------------------------------------
| The maximum number of concurrent requests each worker can handle.
|--------------------------------------------------------------------------
|
| This option controls the maximum number of concurrent requests each worker
| can handle. A higher number can improve throughput but may also increase
| memory usage per worker.
|
*/
'max_concurrent_requests_per_worker' => env('OCTANE_MAX_CONCURRENT_REQUESTS_PER_WORKER', 1000),
/*
|--------------------------------------------------------------------------
| The directory where Octane should store its cache files.
|--------------------------------------------------------------------------
|
| This option controls the directory where Octane should store its cache files.
| Ensure this directory is writable by the web server user.
|
*/
'cache_directory' => storage_path('octane-cache'),
/*
|--------------------------------------------------------------------------
| The directory where Octane should store its logs.
|--------------------------------------------------------------------------
|
| This option controls the directory where Octane should store its logs.
| Ensure this directory is writable by the web server user.
|
*/
'log_directory' => storage_path('octane-logs'),
/*
|--------------------------------------------------------------------------
| The command to use to start the Octane server.
|--------------------------------------------------------------------------
|
| This option controls the command to use to start the Octane server.
| You can customize this command to include specific flags or options
| for your chosen server.
|
*/
'command' => [
'swoole' => 'php artisan octane:swoole',
'roadrunner' => 'rr serve -c rr.yaml',
'frankenphp' => 'php artisan octane:frankenphp',
],
/*
|--------------------------------------------------------------------------
| The command to use to stop the Octane server.
|--------------------------------------------------------------------------
|
| This option controls the command to use to stop the Octane server.
|
*/
'stop_command' => [
'swoole' => 'pkill -SIGTERM -f "artisan octane:swoole"',
'roadrunner' => 'pkill -SIGTERM -f "rr serve"',
'frankenphp' => 'pkill -SIGTERM -f "artisan octane:frankenphp"',
],
/*
|--------------------------------------------------------------------------
| The command to use to restart the Octane server.
|--------------------------------------------------------------------------
|
| This option controls the command to use to restart the Octane server.
|
*/
'restart_command' => [
'swoole' => 'pkill -SIGUSR2 -f "artisan octane:swoole" || php artisan octane:swoole',
'roadrunner' => 'pkill -SIGUSR2 -f "rr serve" || rr serve -c rr.yaml',
'frankenphp' => 'pkill -SIGUSR2 -f "artisan octane:frankenphp" || php artisan octane:frankenphp',
],
/*
|--------------------------------------------------------------------------
| The command to use to reload the Octane server.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reload the Octane server.
| This is useful for applying code changes without a full restart.
|
*/
'reload_command' => [
'swoole' => 'pkill -SIGHUP -f "artisan octane:swoole"',
'roadrunner' => 'pkill -SIGHUP -f "rr serve"',
'frankenphp' => 'pkill -SIGHUP -f "artisan octane:frankenphp"',
],
/*
|--------------------------------------------------------------------------
| The command to use to warm the application.
|--------------------------------------------------------------------------
|
| This option controls the command to use to warm the application.
| This is useful for pre-loading application components.
|
*/
'warm_command' => [
'swoole' => 'php artisan octane:swoole --warm',
'roadrunner' => 'rr serve -c rr.yaml --warm',
'frankenphp' => 'php artisan octane:frankenphp --warm',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's cache.
|
*/
'clear_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's cache.
|
*/
'flush_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's cache.
|
*/
'reset_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's configuration cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's configuration cache.
|
*/
'clear_config_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-config-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-config-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-config-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's configuration cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's configuration cache.
|
*/
'flush_config_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-config-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-config-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-config-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's configuration cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's configuration cache.
|
*/
'reset_config_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-config-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-config-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-config-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's route cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's route cache.
|
*/
'clear_route_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-route-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-route-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-route-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's route cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's route cache.
|
*/
'flush_route_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-route-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-route-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-route-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's route cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's route cache.
|
*/
'reset_route_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-route-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-route-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-route-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's view cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's view cache.
|
*/
'clear_view_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-view-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-view-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-view-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's view cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's view cache.
|
*/
'flush_view_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-view-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-view-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-view-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's view cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's view cache.
|
*/
'reset_view_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-view-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-view-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-view-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's event cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's event cache.
|
*/
'clear_event_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-event-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-event-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-event-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's event cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's event cache.
|
*/
'flush_event_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-event-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-event-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-event-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's event cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's event cache.
|
*/
'reset_event_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-event-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-event-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-event-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's listener cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's listener cache.
|
*/
'clear_listener_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-listener-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-listener-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-listener-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's listener cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's listener cache.
|
*/
'flush_listener_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-listener-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-listener-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-listener-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's listener cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's listener cache.
|
*/
'reset_listener_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-listener-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-listener-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-listener-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's binding cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's binding cache.
|
*/
'clear_binding_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-binding-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-binding-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-binding-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's binding cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's binding cache.
|
*/
'flush_binding_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-binding-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-binding-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-binding-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's binding cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's binding cache.
|
*/
'reset_binding_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-binding-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-binding-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-binding-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's provider cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's provider cache.
|
*/
'clear_provider_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-provider-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-provider-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-provider-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's provider cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's provider cache.
|
*/
'flush_provider_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-provider-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-provider-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-provider-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's provider cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's provider cache.
|
*/
'reset_provider_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-provider-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-provider-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-provider-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's singleton cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's singleton cache.
|
*/
'clear_singleton_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-singleton-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-singleton-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-singleton-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's singleton cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's singleton cache.
|
*/
'flush_singleton_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-singleton-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-singleton-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-singleton-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's singleton cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's singleton cache.
|
*/
'reset_singleton_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-singleton-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-singleton-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-singleton-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's container cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's container cache.
|
*/
'clear_container_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-container-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-container-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-container-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's container cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's container cache.
|
*/
'flush_container_cache_command' => [
'swoole' => 'php artisan octane:swoole --flush-container-cache',
'roadrunner' => 'rr serve -c rr.yaml --flush-container-cache',
'frankenphp' => 'php artisan octane:frankenphp --flush-container-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to reset Octane's container cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to reset Octane's container cache.
|
*/
'reset_container_cache_command' => [
'swoole' => 'php artisan octane:swoole --reset-container-cache',
'roadrunner' => 'rr serve -c rr.yaml --reset-container-cache',
'frankenphp' => 'php artisan octane:frankenphp --reset-container-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to clear Octane's dependency cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to clear Octane's dependency cache.
|
*/
'clear_dependency_cache_command' => [
'swoole' => 'php artisan octane:swoole --clear-dependency-cache',
'roadrunner' => 'rr serve -c rr.yaml --clear-dependency-cache',
'frankenphp' => 'php artisan octane:frankenphp --clear-dependency-cache',
],
/*
|--------------------------------------------------------------------------
| The command to use to flush Octane's dependency cache.
|--------------------------------------------------------------------------
|
| This option controls the command to use to flush Octane's dependency cache.
|
*/
'flush_dependency_cache_command' => [
'swoole' => 'php artisan octane: