<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Anass Ez-zouaine — Senior Backend Engineer · Software Architect · AI Engineer — Laravel</title><description>Laravel posts from Anass Ez-zouaine — Senior Backend Engineer · Software Architect · AI Engineer.</description><link>https://ansezz.com/</link><item><title>Laravel Octane: making PHP scream on high-traffic apps</title><link>https://ansezz.com/blog/laravel-octane-high-traffic/</link><guid isPermaLink="true">https://ansezz.com/blog/laravel-octane-high-traffic/</guid><description>Standard PHP rebuilds the whole kitchen for every request. Laravel Octane boots once, stays warm in memory, and feeds requests through a worker pool — taking 50ms responses down to single digits. Swoole vs RoadRunner, worker-pool tuning, and surviving persistent state without memory leaks.</description><pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Standard PHP is a bit like a restaurant that fires its entire staff and rebuilds the kitchen from scratch for every single customer order. You walk in, they hire a chef, buy a stove, cook your meal, and then demolish the building as soon as you leave. It&apos;s consistent and safe, but it&apos;s a massive waste of energy when you&apos;re trying to serve thousands of people at once.&lt;/p&gt;
&lt;p&gt;This is the PHP-FPM lifecycle. Every request boots the entire Laravel framework, loads your service providers, parses your config, and instantiates your objects. For low-traffic sites, it&apos;s fine. But when you hit real scale, those milliseconds of &quot;boot time&quot; become a wall you can&apos;t climb without throwing excessive amounts of expensive hardware at the problem. Horizontal scaling buys you room, but it doesn&apos;t fix the underlying latency.&lt;/p&gt;
&lt;p&gt;The solution is to stop rebuilding the kitchen. Laravel Octane changes the game by booting your application once, keeping it in memory, and then feeding requests to it through a high-performance worker pool. It transforms PHP from a &quot;short-lived script&quot; language into a &quot;long-lived process&quot; powerhouse.&lt;/p&gt;
&lt;h2&gt;Why your app feels heavy&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/laravel-octane-high-traffic/fpm-vs-octane.webp&quot; alt=&quot;PHP-FPM rebuilding the framework each request versus Octane staying warm&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The overhead of traditional PHP isn&apos;t just about speed; it&apos;s about efficiency. In a standard request, your CPU spends a significant chunk of time just getting the application ready to do work. Once it finally starts execution, it does the database query, renders the view, and then dies.&lt;/p&gt;
&lt;p&gt;If you&apos;re running a complex Laravel monolith with dozens of packages and custom service providers, your boot time might be 30ms to 50ms before a single line of your actual business logic even runs. Under high traffic, this leads to CPU thrashing. You&apos;re paying for the &quot;setup&quot; over and over again.&lt;/p&gt;
&lt;p&gt;Laravel Octane removes this boot cycle. By using high-performance application servers like Swoole or RoadRunner, your app stays resident in memory. The first request boots the framework, and subsequent requests hit a &quot;warm&quot; application. We&apos;re talking about moving from 50ms responses to sub-10ms responses just by changing how the process is managed.&lt;/p&gt;
&lt;h2&gt;Swoole vs RoadRunner: choosing your engine&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/laravel-octane-high-traffic/swoole-vs-roadrunner.webp&quot; alt=&quot;Swoole and RoadRunner compared as Octane application servers&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When you drop Octane into your project, you have to choose between two primary engines: Swoole and RoadRunner. I&apos;ve used both in production, and while they both solve the &quot;persistent state&quot; problem, they do it differently.&lt;/p&gt;
&lt;h3&gt;Swoole&lt;/h3&gt;
&lt;p&gt;Swoole is a C++ extension for PHP. It&apos;s essentially a high-performance networking engine that allows PHP to handle asynchronous tasks, coroutines, and long-lived connections.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros&lt;/strong&gt; — it is incredibly fast. Because it lives as an extension, it has deep access to PHP&apos;s internals. It also gives you access to the &quot;Octane cache,&quot; an in-memory store that&apos;s significantly faster than Redis for local data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons&lt;/strong&gt; — it can be a bit of a nightmare to install and debug. Because it&apos;s a binary extension, you have to compile it or find the right package for your OS. Xdebug doesn&apos;t always play nice with it, and it can be picky about your environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;RoadRunner&lt;/h3&gt;
&lt;p&gt;RoadRunner is written in Go. It acts as a load balancer and process manager that communicates with your PHP workers via a high-speed binary protocol (Goridge).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pros&lt;/strong&gt; — no extensions required. It&apos;s a single binary you drop into your project. It&apos;s much easier to set up in a &lt;a href=&quot;https://ansezz.com/blog/coolify-docker-saas-hosting/&quot;&gt;Docker container&lt;/a&gt; and generally feels more &quot;cloud-native.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons&lt;/strong&gt; — it&apos;s slightly slower than Swoole because of the communication overhead between the Go binary and the PHP processes, though for 99% of apps, this difference is negligible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For most developers starting out with Octane, I recommend RoadRunner for the ease of use. If you are chasing every last millisecond and need features like async task workers, go with Swoole.&lt;/p&gt;
&lt;h2&gt;Tuning your worker pool&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/laravel-octane-high-traffic/worker-pool.webp&quot; alt=&quot;A pool of warm Octane workers handling incoming requests&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The secret sauce of Octane is the &quot;worker pool.&quot; Instead of one process per request, you have a fixed number of workers waiting to handle incoming traffic. If you misconfigure this, you&apos;ll either leave performance on the table or crash your server.&lt;/p&gt;
&lt;p&gt;A general rule of thumb for sizing your worker pool depends on whether your app is CPU-bound or I/O-bound.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CPU-bound apps&lt;/strong&gt; — if you&apos;re doing heavy data processing or image manipulation, set your worker count to the number of CPU cores you have. Adding more workers will just cause context switching and slow things down.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I/O-bound apps&lt;/strong&gt; — most web apps spend 90% of their time waiting for a database, Redis, or an external API. In this case, you can scale your workers to 2x or even 4x your core count. This allows one worker to wait for the DB while another handles a new request.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# Starting Octane with 16 workers for an I/O-heavy app
php artisan octane:start --server=swoole --workers=16
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&apos;t forget about &lt;strong&gt;task workers&lt;/strong&gt; if you&apos;re using Swoole. These are separate from your HTTP workers and are perfect for offloading slow tasks like sending emails or processing webhooks without blocking the main request cycle.&lt;/p&gt;
&lt;h2&gt;The danger of persistent state&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/laravel-octane-high-traffic/memory-leaks.webp&quot; alt=&quot;Hunting down a memory leak in a long-lived Octane worker&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The biggest hurdle when moving to Octane is the shift in mindset. In traditional PHP, &quot;leaky&quot; code doesn&apos;t matter much because the process dies after 100ms. In Octane, a memory leak is a ticking time bomb.&lt;/p&gt;
&lt;p&gt;If you have a static array in a service provider that you append to on every request, that array will grow until your server runs out of RAM. You have to be extremely careful with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Static properties&lt;/strong&gt; — avoid using them for request-specific data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Singletons&lt;/strong&gt; — if you register a singleton in your app container, it stays alive. If that singleton caches data, you need to make sure that data is cleared or managed properly between requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global state&lt;/strong&gt; — avoid &lt;code&gt;global&lt;/code&gt; variables at all costs (which you should be doing anyway).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Laravel helps you by &quot;resetting&quot; some core services between requests, but it can&apos;t catch everything. If you&apos;re migrating an old codebase from &lt;a href=&quot;https://ansezz.com/blog/monolith-to-microservices/&quot;&gt;monolith to microservices&lt;/a&gt;, you&apos;ll want to audit your service providers for any long-lived state.&lt;/p&gt;
&lt;h3&gt;Protecting yourself with max-requests&lt;/h3&gt;
&lt;p&gt;The best insurance policy against memory leaks is the &lt;code&gt;--max-requests&lt;/code&gt; flag. This tells Octane to kill and restart a worker after it has handled a certain number of requests.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Restart workers every 1000 requests to prevent memory bloat
php artisan octane:start --max-requests=1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps your memory usage predictable while still giving you the performance benefits of a warm application.&lt;/p&gt;
&lt;h2&gt;Real-world optimization tips&lt;/h2&gt;
&lt;p&gt;Once you have Octane running, there are a few technical levers you can pull to squeeze out even more performance.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Connection pooling&lt;/strong&gt; — ensure your database connections are persistent. In Octane, your workers stay alive, so they can hold onto their DB connections instead of reconnecting every time. Check your &lt;code&gt;database.php&lt;/code&gt; config and ensure you aren&apos;t hitting the max connection limit on your DB server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Octane cache&lt;/strong&gt; — if you&apos;re using Swoole, use &lt;code&gt;Octane::cache()&lt;/code&gt;. It&apos;s an in-memory table that&apos;s blistering fast. Use it for frequently accessed configuration or small datasets that don&apos;t change often.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bytecode caching&lt;/strong&gt; — make sure OPcache is enabled and tuned. Set &lt;code&gt;opcache.validate_timestamps=0&lt;/code&gt; in production since your code won&apos;t be changing while the server is running.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graceful reloads&lt;/strong&gt; — when you deploy new code, use &lt;code&gt;php artisan octane:reload&lt;/code&gt;. This will gracefully restart the workers without dropping current connections. It&apos;s essential for zero-downtime deployments.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Takeaways for the high-traffic dev&lt;/h2&gt;
&lt;p&gt;Transitioning to Octane isn&apos;t just about installing a package; it&apos;s about maturing your infrastructure.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Identify the bottleneck&lt;/strong&gt; — only use Octane if your boot time is the problem. If your database queries take 2 seconds, Octane won&apos;t help you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test for leaks&lt;/strong&gt; — watch how your app behaves under sustained load and profile its memory growth over time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor workers&lt;/strong&gt; — keep an eye on your CPU and RAM usage to find the &quot;sweet spot&quot; for your worker count.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leverage concurrency&lt;/strong&gt; — use &lt;code&gt;Octane::concurrently()&lt;/code&gt; to execute multiple tasks at once and return their results, cutting down total response time for complex pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Octane takes PHP to a level where it can compete with Node.js and Go for high-concurrency applications while keeping the developer experience of Laravel intact. If you&apos;re building a SaaS that expects a lot of noise, this is your jet engine.&lt;/p&gt;
&lt;p&gt;Are you running Octane in production yet, or is the fear of memory leaks keeping you on PHP-FPM? Drop a note via &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;contact&lt;/a&gt; — let&apos;s talk worker counts. 🤘&lt;/p&gt;
</content:encoded><category>laravel</category><category>laravel</category><category>octane</category><category>php</category><category>performance</category><category>swoole</category><category>roadrunner</category><category>scaling</category><category>high-traffic</category></item><item><title>Laravel multi-tenancy: how I built a scalable SaaS architecture</title><link>https://ansezz.com/blog/laravel-multi-tenancy/</link><guid isPermaLink="true">https://ansezz.com/blog/laravel-multi-tenancy/</guid><description>Single DB vs multi-DB, global scopes that stop data leaks, stancl/tenancy in production, isolated storage, automated migrations, and the Docker + Google Cloud setup I run for high-trust SaaS clients.</description><pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I still remember the panic of my first SaaS launch. I was watching the logs as the third customer signed up. Suddenly I realized I had no idea if customer A could see customer B&apos;s data. That realization is a rite of passage for every developer.&lt;/p&gt;
&lt;p&gt;Building a software-as-a-service (SaaS) platform is a massive technical challenge. The biggest hurdle is almost always data isolation. You need to ensure that every tenant feels like they have the whole application to themselves. If you get this wrong early on, it will haunt you forever.&lt;/p&gt;
&lt;p&gt;I have spent years refining a scalable architecture using Laravel. It is the most robust way to handle multiple customers on a single codebase. Here is how I approach the architecture to ensure security and scalability.&lt;/p&gt;
&lt;h2&gt;Why simple database structures fail SaaS&lt;/h2&gt;
&lt;p&gt;Most developers start with a single database. They add a &lt;code&gt;user_id&lt;/code&gt; or &lt;code&gt;team_id&lt;/code&gt; to every table. It works fine for the first ten users. Then the complexity grows.&lt;/p&gt;
&lt;p&gt;You start adding more relationships. You forget to add a &lt;code&gt;where&lt;/code&gt; clause in one obscure controller. Suddenly one customer is seeing another customer&apos;s private invoices. This is a catastrophic failure. It kills trust and can end your business overnight.&lt;/p&gt;
&lt;p&gt;Performance also becomes a nightmare. As your database grows to millions of rows the queries get slower. Indexing helps, but it does not solve the fundamental problem of data bloat. You need a strategy that isolates data while keeping your infrastructure manageable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/laravel-multi-tenancy/architecture.webp&quot; alt=&quot;Tenant isolation architecture diagram&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Choosing your isolation strategy&lt;/h2&gt;
&lt;p&gt;When I build custom web solutions I always start by choosing between two main paths. You either go with a single database or a multi-database setup.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;single database&lt;/strong&gt; approach uses a shared schema. Every row has a &lt;code&gt;tenant_id&lt;/code&gt;. It is cheap to run and easy to update. I recommend this for startups where costs need to stay low. You can manage thousands of small tenants on a single server this way.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;multi-database&lt;/strong&gt; approach is the gold standard for enterprise. Every customer gets their own database. This offers the best security and makes backups easy. If one database crashes the others stay online. I use this for high-value clients who have strict compliance needs.&lt;/p&gt;
&lt;p&gt;I often use the &lt;code&gt;stancl/tenancy&lt;/code&gt; package for Laravel. It is the most flexible tool in the ecosystem. It allows you to switch between these strategies as your business grows. You can find out more about how I handle these complex technical challenges on the &lt;a href=&quot;https://ansezz.com/work/&quot;&gt;Ansezz work page&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Building with global scopes&lt;/h2&gt;
&lt;p&gt;The secret to sleeping well at night is automation. I never rely on my memory to filter data. Instead I use Laravel global scopes.&lt;/p&gt;
&lt;p&gt;A global scope automatically adds a filter to every query on a model. It ensures that &lt;code&gt;Customer::all()&lt;/code&gt; only returns customers for the current tenant. It happens behind the scenes so you cannot forget it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/laravel-multi-tenancy/code-snippet.webp&quot; alt=&quot;Annotated code snippet showing a BelongsToTenant trait&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I create a &lt;code&gt;BelongsToTenant&lt;/code&gt; trait. I apply this trait to every model that needs isolation. It handles the filtering and automatically sets the &lt;code&gt;tenant_id&lt;/code&gt; when a new record is created. It is a simple solution that prevents 99 percent of data leaks.&lt;/p&gt;
&lt;p&gt;Here&apos;s what that trait looks like in practice:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\Concerns;

use App\Models\Tenant;
use App\Scopes\TenantScope;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

trait BelongsToTenant
{
    protected static function bootBelongsToTenant(): void
    {
        static::addGlobalScope(new TenantScope());

        static::creating(function ($model): void {
            if (! $model-&amp;gt;tenant_id &amp;amp;&amp;amp; app()-&amp;gt;bound(&apos;tenant&apos;)) {
                $model-&amp;gt;tenant_id = app(&apos;tenant&apos;)-&amp;gt;id;
            }
        });
    }

    public function tenant(): BelongsTo
    {
        return $this-&amp;gt;belongsTo(Tenant::class);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You also need to isolate your cache and your file storage. If two tenants upload a file named &lt;code&gt;logo.png&lt;/code&gt; they should not overwrite each other. I configure Laravel to use tenant-specific prefixes for all storage paths. This creates a true &quot;sandbox&quot; environment for every user.&lt;/p&gt;
&lt;h2&gt;Scaling on the cloud&lt;/h2&gt;
&lt;p&gt;Your architecture is only as good as the infrastructure it runs on. I usually deploy my Laravel apps using Docker and cloud providers like Google Cloud or AWS.&lt;/p&gt;
&lt;p&gt;Containerization is key. It allows me to scale the application horizontally. When traffic spikes I can spin up more instances of the web server. Because the multi-tenancy logic is handled at the application level, the infrastructure stays clean.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/laravel-multi-tenancy/infrastructure.webp&quot; alt=&quot;Cloud infrastructure diagram for a multi-tenant Laravel deployment&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I use managed database services like Google Cloud SQL. They handle the heavy lifting of backups and scaling. For multi-database setups I use automated scripts to provision new databases whenever a customer signs up. This &quot;infrastructure as code&quot; approach ensures that scaling is a button click away.&lt;/p&gt;
&lt;p&gt;If you are looking to modernize your digital presence with a scalable cloud setup you can see my full range of services at &lt;a href=&quot;https://ansezz.com/&quot;&gt;ansezz.com&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The tenant switcher experience&lt;/h2&gt;
&lt;p&gt;The final piece of the puzzle is the user interface. Customers need a seamless way to move between different accounts if they own multiple businesses.&lt;/p&gt;
&lt;p&gt;I build clean dashboards using Vue. The frontend communicates with the Laravel backend via GraphQL APIs. This setup allows for a very fast and reactive user experience. The tenant switcher is always accessible and shows the user exactly which context they are working in.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/laravel-multi-tenancy/tenant-switcher.webp&quot; alt=&quot;Tenant switcher UI mockup in a Vue dashboard&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I focus on making the transition between tenants feel instant. I cache the tenant configuration in the frontend to avoid unnecessary API calls. It is these small details that separate a basic app from a high-quality pro solution.&lt;/p&gt;
&lt;h2&gt;My SaaS architecture checklist&lt;/h2&gt;
&lt;p&gt;If you are starting a new project today, here are the steps I recommend you follow.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Choose your package early.&lt;/strong&gt; I prefer &lt;code&gt;stancl/tenancy&lt;/code&gt; because of its flexibility. It handles subdomain routing and database switching out of the box.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement global scopes immediately.&lt;/strong&gt; Do not wait until you have ten models. Add the trait to your base model and make it a standard part of your workflow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automate your deployment.&lt;/strong&gt; Use Docker from day one. It makes local development identical to production. This avoids the &quot;it works on my machine&quot; bugs that plague SaaS launches.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan for data migration.&lt;/strong&gt; As you update your schema you need a way to run migrations across hundreds of databases. Tools like &lt;code&gt;stancl/tenancy&lt;/code&gt; have built-in commands for this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep it simple.&lt;/strong&gt; Don&apos;t build a multi-database setup if you only have five users. Start small and scale as the revenue grows. My goal is always to deliver exceptional results without over-complicating the technical stack.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Are you building a SaaS or looking to migrate your current app to a multi-tenant structure? What is the biggest technical hurdle you are facing right now? &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;Get in touch&lt;/a&gt; — I&apos;d love to hear about it. 🤙&lt;/p&gt;
</content:encoded><category>laravel</category><category>laravel</category><category>multi-tenancy</category><category>saas</category><category>architecture</category><category>stancl-tenancy</category><category>docker</category><category>postgres</category></item></channel></rss>